I’m trying to implement a simple todo app with react-native and zustand persist, the code without the persist
method is working fine, but when I try to introduce persist, I run into errors or I’m not able to correctly implement it, I went through the documentation on zustand page, but it is not working. Below is the relevant code and errors:
App.tsx
const App = () => {
const { todos, addTodo, getTodos } = useTodoStore((state: TodoState) => state);
useEffect(() => {
getTodos();
}, []);
return (
...
)
}
zustand store:
import create from 'zustand';
import { persist } from 'zustand/middleware';
import AsynStorage from '@react-native-async-storage/async-storage';
export interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: string;
}
export interface TodoState {
todos: Todo[];
addTodo: (todo: Todo) => void;
getTodos: () => void;
removeTodo: (id: string) => void;
toggleTodo: (id: string) => void;
}
export const useTodoStore = create<TodoState>(
// @ts-ignore
persist(
set => ({
todos: [],
getTodos: () => {
AsynStorage.getItem('todo-storage').then(todos => {
if (todos) {
set(state => ({ ...state, todos: JSON.parse(todos) }));
}
});
},
addTodo: (todo: Todo) => {
set(state => {
console.log('state: ', state.todos);
return {
...state,
todos: [...state.todos, todo],
};
});
},
removeTodo: (id: string) => {
set(state => ({
...state,
todos: state.todos.filter(todo => todo.id !== id),
}));
},
toggleTodo: (id: string) => {
set(state => ({
...state,
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo,
),
}));
},
}),
{
name: 'todo-storage',
getStorage: () => AsynStorage,
},
),
);
Now, when I try to add a todo, I run into the following error:
ERROR TypeError: Invalid attempt to spread non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.
So, does anyone have a code example with persist (react-native), I don’t understand what I’m doing wrong here, I’m doing as shown in docs.
EDIT:
If I remove persist
middleware and all code related to it, it works just fine, also, to note:
When I use persist
middleware with async-storage
, the state is this:
LOG state: {"state": {"_hasHydrated": true, "todos": {"state": [Object], "version": 0}}, "version": 0}
And when I don’t use persist
it works:
LOG state: [{"completed": false, "createdAt": "Fri Jul 22 2022 14:27:47 GMT+0530 (IST)", "id": "6bfdab5e34b198", "text": "Let’s seeeee"}]
2
Answers
I don’t know how zustand handles storages other than the default ones on the web, but I’m assuming perhaps the API doesn’t match with the react-native-async-storage. I would suggest you implement the get and set methods of your storage manually like they have explained in the docs here: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#how-can-i-use-a-custom-storage-engine
I haven’t tried running your code yet. However, you don’t need to manually call
AsyncStorage.getItem()
to restore the saved store. Thepersist
middleware handles that.getTodos
, which I suspect is the errant action, is therefore unnecessary. I further suspect that zustandpersist
adds extra metadata to the serialized data, which is what you see when you manually log it out.See this example on the general structure (actions elided).