简体   繁体   中英

Vue component doesn't update when state in store is updated

I'm trying to do a simple todo list in Vue but I want to abstract everything out and use a dummy REST API so I can start to get used to production-level projects in Vue and it's all making my head spin. GET, PUT, and POST requests seem to be working, but I can't figure out why the list of todos doesn't update automatically when I do a successful POST request to the back end.

I've got a TodoList component that loops through a todosFiltered() computed property to show the todos. The computed property refers back to the getter todosFiltered in the Vuex store. I also use the created() lifecycle hook here to dispatch an action in the store that makes the initial GET request and then populates an array called todos in the store when the page is first loaded. The getter todosFiltered in the store returns state.todos , so I assumed that when my component re-renders, it would have the new todos array from the state grabbed from todosFiltered , only that's not happening. What am I missing here? Any advice would be greatly appreciated.

TodoList.vue

(I know I'll have to work out a solution for the ids, it's on my list :p)

<template>
    <div class="container">
        <input v-model="newTodo" type="text" placeholder="What must be done?" class="todo-input" @keyup.enter="addTodo">
        <transition-group name="fade" enter-active-class="animated zoomIn" leave-active-class="animated zoomOut">
            <todo-item v-for="todo in todosFiltered" :key="todo.id" :checkAll="!anyRemaining" :todo="todo"></todo-item>
        </transition-group>
        <div class="extra-container">
            <todos-filtered></todos-filtered>
        </div>
    </div>
</template>

<script>
import TodosFiltered from './TodosFiltered'
import TodoItem from './TodoItem'

export default {
  name: 'todolist',
  components: {
    TodosFiltered,
    TodoItem
  },
  data() {
    return {
        beforeEditCache: '',
        newTodo: '',
        idForTodo: 10,
    }
  },
  // Methods
  methods: {
    addTodo() {
        if (this.newTodo.trim().length == 0) {
            return
        }
        this.$store.dispatch('addTodo', {
            id: this.idForTodo,
            title: this.newTodo,
            completed: false
        })
        this.newTodo = ''
        this.idForTodo++
    }
  },
  computed: {
    todosFiltered() {
        return this.$store.getters.todosFiltered
    },
  },
  created() {
    this.$store.dispatch('loadTodos')
  },
}
</script>

store.js

export const store = new Vuex.Store({
    state: {
        filter: 'all',
        todos: []
    },
    getters: {
        todosFiltered(state) {
            if (state.filter == 'all') {
                return state.todos
            } else if (state.filter == 'active') {
                return state.todos.filter(todo => !todo.completed)
            } else if (state.filter == 'completed') {
                return state.todos.filter(todo => todo.completed)
            }
            return state.todos
        },
        showClearCompleted(state) {
            return state.todos.filter(todo => todo.completed).length > 0
        }
    },
    mutations: {
        addTodo(state, todo) {
            state.todos.push(todo)
        },
        setTodos(state, todos) {
            state.todos = todos
        },
    },
    actions: {
        loadTodos(context) {
            axios.get('http://localhost:3000/todos')
            .then(r => r.data)
            .then(todos => {
                context.commit('setTodos', todos)
            })
        },
        updateTodo(context, todo) {
            axios.put('http://localhost:3000/todos/' + todo.id, {
                "id": todo.id,
                "title": todo.title,
                "completed": todo.completed
            })
        },
        addTodo(context, todo) {
            axios.post('http://localhost:3000/todos', {
                "id": todo.id,
                "title": todo.title,
                "completed": todo.completed
            })
            .then(todo => {
                context.commit('addTodo', todo)
            })
        },
    }
})

EDIT: Here's what's going on in Vue Dev Tools when I add a todo -- todos in the store's state gets updated immediately, and the todosFiltered computed property in the TodoList component ALSO reflects that -- but the new todo doesn't appear in the list! Strange.

The problem is using a $store.getter on the v-for loop. Try the following:

  1. Set your computed to

    todos() { return this.$store.todos; }

  2. Change your v-for loop to use todo in todos

  3. Add a v-if condition to the loop like v-if="filtered(todo)"
  4. Create a new method called filtered (or whatever you prefer), and add your "filteredTodos" logic there, returning true/false as needed
  5. If you need to share this code, you can always use a mixin and share it between your components

Hope this works for you

A way to solve this can be to create what I like to call a refresh() method.

Basically, you will have a local list of todos in your data() method, the refresh() method will load all the todos from the store into the local todos list, every time you do an action, such as creating, deleting, or updating, you would call the refresh method to re-load the list for you.

So, in your TodoList.vue :

<template>
    <todo-item v-for="todo in todosFiltered" :key="todo.id"></todo-item>
</template>

<script>
export default {
    data() {
        return {
            // Where we store the local list of Todos 
            // so the component will react when we do something to it
            todosFiltered: []
        }
    },
    methods {
        refresh() {
            // Get the todo list from the store.
            // which in turn will trigger a change event so the component
            // react to what we did.
            this.todosFiltered = this.$store.getters.todosFiltered;
        },
        addTodo() {
            this.$store.dispatch('addTodo').then(() => {
                // Refresh the list after adding a new Todo
                this.refresh();
            })
        },
        updateTodo() {
            this.$store.dispatch('updateTodo').then(() => {
                // Refresh the list after updating a Todo
                this.refresh();
            })
        },
        deleteTodo() {
            this.$store.dispatch('deleteTodo').then(() => {
                // Refresh the list after deleting a Todo
                this.refresh();
            })
        }
    },
    created() {
        this.$store.dispatch('loadTodos').then( () => {
            // Refresh the list when first loaded after the Todos been saved in Vuex
            this.refresh();
        })
    }
}
</script>

Don't actually delete what you already have and replace it with this, just apply what's here to your code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM