-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SSR issues with Next.js (and persisting the data) #324
Comments
Edit: I figured out that I could fix this issue by wrapping it into its own component and then dynamically loading it with SSR disabled. But I am not sure if this is the only solution.. |
Hi 👋. The storage is only available on the client, so on the server (while SSR) the store will have the default values. If you want to fix this warning you must not set the value in the DOM while SSR. For example, it can be done with a simple condition based on this |
#284 doesn't seem to work though 😕 |
Is there an example of what this looks like? 🤔 |
@sandren ah, my bad. This part: "it can be done with a simple condition based on this persist option" is wrong. I thought it'd be sufficient, but apparently it isn't. |
I'll close this for now. @gino let me know if your issue isn't solved 🙂 |
@AnatoleLucet I'd suggest guys to add this into the README in the persist section. I see a lot of questions being raised regarding this. And the sandbox is really helpful. Thanks! |
@Saulius-HALO I agree. Though we've planned to create a gh wiki for |
@AnatoleLucet would you be so kind and provide or edit the codesandbox with example on how to incorporate |
@Saulius-HALO sure. const useMyStore = create(storage(
...,
{
name: "my-store",
serialize: (state) => btoa(JSON.stringify(state)),
deserialize: (storedState) => JSON.parse(atob(storedState)),
}
)) https://codesandbox.io/s/nextjs-zustand-example-forked-bnpfz?file=/store/useUserStore.ts |
Isn't there a way to save this information also on the server side? I found a form with cookies, but I would have to use GetServerSideProps on every page I needed and I don't want that. |
@sidneypp Although this is a bit hacky, I think it should work with some kind of custom storage. const useMyStore = create(storage(
...,
{
name: "my-store",
getStorage: () => ({
setItem: (key, value) => {
// your own logic
},
getItem: (key) => {
// still your own logic
},
})
}
)) |
Is there a way to make this work by default? ie. something like const myThing = useStoreSSR(state => state.myThing)
return <RenderMyThing myThing={myThing} /> Where const [myThing, setMyThing] = useState(defaultThing)
useEffect(() => setMyThing(getMyThingFromStorage()), [])
return <RenderMyThing myThing={myThing} /> But writing the generic I specifically want this because it leads to cleaner code, say I have const postsRead = useStore(state => state.postsRead)
return <ul>
{posts.map(post =>
<li style={color: postsRead.includes(post.slug) ? "green" : "yellow"}>{post.title}</li>
)}
</ul> This code (which is the obvious thing to write) leads to hydration mismatches and wierd bugs (don't ask or you'll trigger my ptsd). I feel this is the opposite of the pit of success It's perfectly fine to SSR this with const hydrated = useHasHydrated()
const postsReadZustand = useStore(state => state.postsRead)
const postsRead = hydrated ? postsReadZustand : []
return <ul>{posts.map(...)}</ul> Which isn't much worse, but I feel like this should "just work" and not require boilerplate and duplicating the defaults. Edit: I managed to sort of implement // Separation is needed because we can't instantiate
// state functions without a reference to set and get
type StateData = { posts: []Post };
type StateFunctions = { /* ... */ };
type State = StateData & StateFunctions;
const defaultState: StateData = { posts: [] };
export const useStoreSSR = <U>(selector: StateSelector<StateData, U>) => {
const defaultValue = selector(defaultState);
const [value, setValue] = useState(defaultValue);
const zustandValue = useStore(selector);
useEffect(() => setValue(zustandValue), []);
return value;
}; Now the hydration error is gone, but if it's this annoying to write |
@UlisseMini as shown in the doc, this is intentional. One way to get the behavior you want is to use an asynchronous storage. export const asyncPersist = (config, options) => {
const { getStorage } = options;
options.getStorage = () => {
const { setItem, getItem, removeItem } = getStorage();
return {
setItem: async (...args) => setItem(...args),
getItem: async (...args) => getItem(...args),
removeItem: async (...args) => removeItem(...args),
}
};
return persist(config, options);
};
const useStore = create(asyncPersist(
(set, get) => ({
...
}),
{
...
}
)) |
Hmm how to solve this... |
I don't get why the hate for this comment. @AnatoleLucet is simply highlighting the default behaviour of the store and showcases two solutions:
However, may I ask, is there a way to use zustand + persist in a hydration sensitive setup like with Next.js, without having to check for hydration every time a value is used? Next.js outlines a solution with What do you think @AnatoleLucet ? |
Hi @gino , could you please show an example of exactly what you ended up wrapping for this? |
below code works for me.
|
@alvin30595 your solution worked for me! Thanks. |
|
@MariusVB , @alvinlys 's solution disables SSR all together making the app show a "Loading" indicator instead of showing the actual page. This is not ideal. Not using this solution displays the correct rendered page. Also, the |
I don't fully understand how the hydration process occur, It seems like it's happening twice here. How could Zustand alter the template before the application is hydrated? In my understanding the page is just as static as a text file before hydration but apparently that's not the case. Anyway, in my option it does make sense to wait for Next.js to do whatever it has to do first and then update the template with data stored in the local storage. I found this implementation version which looks nicer:
|
Here is an abstaction I created to have the hydrated state in every store
|
Hi there,
I am loving the way that Zustand works and absolutely enjoy using it, but unfortunately I have issues with the following scenario:
Then I am trying to display the selected user based on the ID that is stored in the store. Through a
array.find()
, where I loop through my users array. Everything works fine so far, but as soon as I want to display a property from that selected user, I get the following error when I refresh the page:This error only happens whenever I refresh the page so it must be an issue with the "persist data" functionality.
I have made a Codesandbox to reproduce this issue and I hope someone is able to help me out with this.
https://codesandbox.io/s/nextjs-zustand-example-z8ujs?file=/pages/index.tsx
Thanks a lot and I hope someone is able to help me out.
Edit: Apparently this issue doesn't even come from the part that filters the users based on the ID. It also throws the error in the console from only showing the "selected user ID" directly from the store, that is persisted. Really strange issue.
The text was updated successfully, but these errors were encountered: