zustand: Persisted state gets thrown away if any state is set in useEffect

I’m having a strange bug where my persisted state is getting overridden.

I’m using Next.js, Zustand with the persist middleware, and localForage using async IndexedDB. I have default values set in my store, but when my app first runs I want to use some different default values in a useEffect depending on whether the user is using a mobile device or not.

According to my mental model of Zustand, what should happen is that the useEffect runs before or after the persisted state is loaded. If it runs before, the persisted state should get merged in with priority. If it runs after, the persisted state should be merged in with the properties in useEffect having priority. What I’d actually like to happen is for the useEffect to get run before, but it doesn’t matter for the purposes of this issue.

Instead, what seems to be happening is that all of my persisted properties get thrown away, and I get the default values along with the values I set in the useEffect. This doesn’t make sense to me, and it seems like a bug. My persisted properties should always stay persisted if I haven’t explicitly overridden them, which in this case I haven’t – all I’ve overridden are some other properties.

If I comment out the useEffect, then it works as you would expect: the persisted state stays persisted.

Hopefully this makes sense. If you’d like to take a look at my code to get a better idea of what I’m talking about, the store is here and the code that runs in the useEffect is here.

Edit: Minimal reproducible case: https://github.com/churichard/zustand-persist-bug

Click the button to increase the number of bears (which is stored in IndexedDB). Once you refresh the page, the bears resets to 0.

Comment out the useEffect to see it work fine and for the number of bears persist.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 16 (6 by maintainers)

Most upvoted comments

I think what you said was slightly different. What I mean is for the initial state to actually be updated in the useEffect, but for that update to not be persisted into IndexedDB. Then, when IndexedDB is read later, it will override both the initial state and whatever happened in the useEffect.

Indeed I misunderstood 😄

This might be a better and more predictable default. And if someone doesn’t want their values to be overridden, they can still work around that with the merge option.

How does this hack fix, or not?

Should work. Though, if there’s no set() afterward, the data won’t be stored.

Thanks for the reproduction! This should help. @AnatoleLucet Can you take a look?