Search code examples
vue.jspinia

Vue3 pinia store is being updated, but not all components in the template are being updated


I have a vue 3 application that stores an array of todo list items (retrieved from HTTP) to a pinia store. I am using pinia state to hold the array of todo items, actions for fetching and adding to the state (directly for now), and getters for filtering the state.

import { defineStore } from "pinia";
import axios from "axios";

export const useTodos = defineStore("todos", {
    state: () => ({
        todos: []
    }),
    actions: {
        async fetchAllTodos() {
                this.todos = res.data
        },
       addTodo(data) {
           this.todos.push(data)
        }
    },
    getters: {
        getIncompleteTodosWithinOneDay: (state) => {
            return state.todos.filter(
                (todo) => {
                    if (todo.completed) {
                        return
                    }
                    if (deadlineInOneDay(todo)) {
                        return todo
                    }
                }
            )
        },
        getAllTodosDeadlineAscending: (state) => {
            return state.todos.sort(
                (t1, t2) => {
                    return convertStringToLuxonDateTime(t1.date_deadline) - convertStringToLuxonDateTime(t2.date_deadline)
                }
            )
        },
    }
})

When adding a todo item to the state, using a test button from within the Debugger component, I can see that the Debugger component's template is updating with the newly added todo item. The dev tools are also showing that the pinia's state > todos Array increased by 1 whenever I click the test button.

However, other components in my app are not updating. I am expecting that all the other components aside from Debugger should also update their template to add the newly added todo item.

Here is how my template is laid out:

<template>
    <Navbar></Navbar>
    <RouterView></RouterView>

    <!-- debugger -->
    <Suspense>
        <template #default>
            <Debugger></Debugger>
        </template>
        <template #fallback>
            <ProgressLoader></ProgressLoader>
        </template>
    </Suspense>
</template>

One of the components, where I expect the new todo item to be added as well (I use a getter method from the store to filter the data)

<script setup>
import { useTodos } from '@/stores/todosStore';

const todosStore = useTodos()
await todosStore.fetchAllTodos()

const tasksToday = await todosStore.getIncompleteTodosWithinOneDay

</script>

<template>
    <h4>Tasks Today</h4>
    {{ tasksToday }}
</template>

My Debugger.vue component, which has a button for adding a dummy todo item and a template for rendering the list of todo items, using a getter.

<script setup>
import { DateTime } from 'luxon';

import { useTodos } from '../stores/todosStore.js'

const todosStore = useTodos()
await todosStore.fetchAllTodos()

const spawnTodo = async () => {
    const test = {
        id: Math.random(),
        content_en: "test",
        content_jp: "test",
        bookmarked: true,
        completed: false,
        date_created: DateTime.now().toISO({ includeOffset: false }),
        date_deadline: DateTime.now().toISO({ includeOffset: false }),
    }
    await todosStore.addTodo(test)
}
</script>

<template>
    <button @click="spawnTodo" class="btn btn-primary">Add Todo</button>

    <h3>All todos in ascending deadline</h3>
    <ul v-for="todo in todosStore.getAllTodosDeadlineAscending" :key="todo.id">
        <li> {{ todo.id }} - {{ todo.content_en }} - {{ todo.content_jp }} </li>
        <li>bookmarked: {{ todo.bookmarked }} Completed: {{ todo.completed }}</li>
</template>

Solution

  • The components contain the unjustified use of async/await. Only fetchAllTodos returns a promise and needs to be awaited.

    The problem is here:

    const tasksToday = await todosStore.getIncompleteTodosWithinOneDay
    

    getIncompleteTodosWithinOneDay returns a new array and can't be dereferenced.

    It should either be consistently used as todosStore.getIncompleteTodosWithinOneDay in components, or converted to a ref:

    const { getIncompleteTodosWithinOneDay: tasksToday } = storeToRefs(todosStore)
    

    Another problem is that getAllTodosDeadlineAscending contains a side effect and mutates the original array, this is the reason it could stay reactive with the same usage as tasksToday. It should be:

    getAllTodosDeadlineAscending: (state) => {
        return [...state.todos].sort(...)