How to restore persisted state on page refresh #1569
-
I'm kind of a noob in state management.... I want to know how to restore the state from indexeddb on page refresh. The state is being set as I want but when I refresh the page it is lost and back to initial. I have this basic setup // auth.store.ts
import { create } from 'zustand'
import { createJSONStorage, persist } from 'zustand/middleware'
import storage from './storage';
interface AuthState {
authUser: Object,
}
interface Actions {
setAuthUser: (user: any) => void
}
export const useAuthStore = create<AuthState & Actions>()(
persist((set, get) => ({
authUser: {},
setAuthUser: (user: any) => set(() => ({ authUser: user }))
}),
{
name: "authstorage",
storage: createJSONStorage(() => storage as any)
}),
) and then in my component after login I have this import { useAuthStore } from "@/stores/auth.store";
const Home = () => {
const user = useAuthStore.getState().authUser
return (<>
<div>
<h3>{JSON.stringify(user)}</h3>
</div>
</>)
}
Home.layout = 'publicLayout';
export default Home; Just after login when I come to the page I have userData but when I refresh it is back to {}. It is stated though that zustand handles persisted state restoring. What am I doing wrong here ? Thanks for any hint. I'm using next.js and localForage as custom storage for zustand persist |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 3 replies
-
Hey I figured out my issue has to do with hydration, since I'm using an async storage. Looked up in the docs and found a solution to my problem. // use-hydration.ts
import { useEffect, useState } from 'react';
import { StoreApi, UseBoundStore, } from 'zustand'
import { StorePersist, Write } from 'zustand/middleware';
export const useHydration = (boundStore: UseBoundStore<Write<StoreApi<any>, StorePersist<any, unknown>>>) => {
const [hydrated, setHydrated] = useState(boundStore.persist.hasHydrated);
useEffect(() => {
const unsubHydrate = boundStore.persist.onHydrate(() => setHydrated(false))
const unsubFinishHydration = boundStore.persist.onFinishHydration(() => setHydrated(true))
setHydrated(boundStore.persist.hasHydrated())
return () => {
unsubHydrate()
unsubFinishHydration()
}
}, []);
return hydrated;
} And then in my component I have the following:
Now it's working as I expect. |
Beta Was this translation helpful? Give feedback.
-
I'd suggest though that the types such as Write, StoreApi, StorePersist and WithDevtools should be exported from the package in future version of zustand. Thanks for this nice package. |
Beta Was this translation helpful? Give feedback.
-
There is a new solution, when I used it I didn't need any extra package or hooks; the solution is In my case code looks like this; import { UserPayload } from 'src/swagger/api/base';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
interface CurrentUserStore {
user: UserPayload;
setUser: (user: UserPayload) => void;
clearUser: () => void;
}
export const useCurrentUserStore = create<CurrentUserStore>()(
persist(
set => ({
user: {} as UserPayload,
setUser: user => set({ user }),
clearUser: () => set({ user: {} as UserPayload }),
}),
{
name: 'currentUser',
storage: createJSONStorage(() => sessionStorage),
// merge works perfect
merge: (persistedState, currentState) => ({ ...currentState, ...(persistedState as CurrentUserStore) }),
},
),
); |
Beta Was this translation helpful? Give feedback.
-
Anyone having this issue on NextJS 15? I am :/ Edit: I had to add a "merged" boolean property to the app store and update it's state in the "merge" function like in the example above (thanks for that). And I only try to fetch data after the merge is complete, if needed. |
Beta Was this translation helpful? Give feedback.
-
On NextJS 15, I'm not really sure, but looks like the hydrate happens after the page load, so when we read the state it has not yet been updated. Also, there seems to be something around localStorage. While trying to solve this, I came up with a combination that fixed it: const Root = ({children}: Props) => {
useEffect(() => {
useUser.persist.rehydrate()
useTasks.persist.rehydrate()
}, [])
return (
<html lang="en" className={'dark'}>
<body
className={cn(inter.className, 'bg-background bg-gray-900')}
suppressHydrationWarning={true}>
{children}
</body>
</html>
)
} I updated it to check if the hydrate has finished
And then, in the store, I updated the state config as the following: {
name: 'someState',
storage: createJSONStorage(() =>
typeof window !== 'undefined'
? localStorage
: {
getItem: () => null,
setItem: () => {},
removeItem: () => {},
}
),
} I don't know if this approach is correct because we're remounting the children, but it's working for me now |
Beta Was this translation helpful? Give feedback.
Hey I figured out my issue has to do with hydration, since I'm using an async storage. Looked up in the docs and found a solution to my problem.
Here is my solution