最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - react native with zustand persist - Stack Overflow

programmeradmin1浏览0评论

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
Add a ment  | 

3 Answers 3

Reset to default 2

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

发布评论

评论列表(0)

  1. 暂无评论