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;
pleted: 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, pleted: !todopleted } : 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: [{"pleted": false, "createdAt": "Fri Jul 22 2022 14:27:47 GMT+0530 (IST)", "id": "6bfdab5e34b198", "text": "Let’s seeeee"}]
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;
pleted: 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, pleted: !todo.pleted } : 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: [{"pleted": false, "createdAt": "Fri Jul 22 2022 14:27:47 GMT+0530 (IST)", "id": "6bfdab5e34b198", "text": "Let’s seeeee"}]
Share
Improve this question
edited Jul 22, 2022 at 9:45
dev1ce
asked Jul 22, 2022 at 5:47
dev1cedev1ce
1,7475 gold badges29 silver badges57 bronze badges
1
- Have you considered using something like mmkv instead? It's synchronous, as opposed to AsyncStorage, and not to mention faster. Check out this gist: github./mrousavy/react-native-mmkv/blob/master/docs/… – ptent Commented Nov 11, 2022 at 17:23
3 Answers
Reset to default 2I 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./pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#how-can-i-use-a-custom-storage-engine
This is the plete answer to the original question.
import { create } from "zustand";
import { persist, createJSONStorage, StateStorage } from "zustand/middleware";
import AsyncStorage from "@react-native-async-storage/async-storage";
const storage: StateStorage = {
getItem: async (name: string): Promise<string | null> => {
console.log(name, "has been retrieved");
const data = (await AsyncStorage.getItem(name)) || null;
console.log("data: ", data);
return data;
},
setItem: async (name: string, value: string): Promise<void> => {
console.log(name, "with value", value, "has been saved");
await AsyncStorage.setItem(name, value);
},
removeItem: async (name: string): Promise<void> => {
console.log(name, "has been deleted");
await AsyncStorage.removeItem(name);
},
};
export interface Todo {
id: string;
text: string;
pleted: boolean;
createdAt: string;
}
export interface TodoState {
todos: Todo[];
add: (todo: Todo) => void;
remove: (id: string) => void;
toggle: (id: string) => void;
}
export const useTodoStore = create<TodoState>()(
persist(
(set) => ({
todos: [],
add: (todo: Todo) => {
set((state) => {
console.log("state: ", state.todos);
return {
...state,
todos: [...state.todos, todo],
};
});
},
remove: (id: string) => {
set((state) => ({
...state,
todos: state.todos.filter((todo) => todo.id !== id),
}));
},
toggle: (id: string) => {
set((state) => ({
...state,
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, pleted: !todo.pleted } : todo
),
}));
},
}),
{
name: "todo-storage",
storage: createJSONStorage(() => storage),
}
)
);
The above code creates a custom storage using AsyncStorage for zustand.
I know, I am late to party but this might help someone.
I haven't tried running your code yet. However, you don't need to manually call AsyncStorage.getItem()
to restore the saved store. The persist
middleware handles that. getTodos
, which I suspect is the errant action, is therefore unnecessary. I further suspect that zustand persist
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).