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>
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(...)