Skip to content
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

fix: delete root keys with immer middleware #2481

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

simlmx
Copy link

@simlmx simlmx commented Apr 12, 2024

Related Issues or Discussions

Fixes #2480

Summary

Currently when calling setState with the immer middleware, replace=false is assumed by default. This means that the old state is merged with immer's produce output. I don't think there is any reason to merge those states: immer's produce already returns a smart version of the whole state where copies are used whenever possible. The merging is mostly equivalent to replace=True, except when keys are deleted: the old deleted keys get merged with the new state.

I'm also assuming that when using the immer middleware, we always want to do something like

store.setState((state) => { state.x = 3})

and never

store.setState({x: 3})

so I simplified the middleware consequently.

I added a test to reproduce the bug and some other simple tests.

Someone with more experience with the immer middleware should double-check all this! There could very well be usage patterns that I'm not aware of.

Check List

  • yarn run prettier for formatting code and docs

@dai-shi @dbritto-dev Let me know if I can improve anything in the PR.

* Fix bug described in pmndrs#2480
* Add basic tests for the immer middleware
Copy link

vercel bot commented Apr 12, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
zustand-demo ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 12, 2024 4:07am

Copy link

codesandbox-ci bot commented Apr 12, 2024

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Copy link
Collaborator

@dbritto-dev dbritto-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review all comments

src/middleware/immer.ts Outdated Show resolved Hide resolved
src/middleware/immer.ts Outdated Show resolved Hide resolved
@dbritto-dev
Copy link
Collaborator

@simlmx we need to add a comment on immer middleware saying that shouldReplace parameter is completely ignore on immer middleware and will be set up to true by default

@connormanning
Copy link

connormanning commented Apr 19, 2024

I'm also assuming that when using the immer middleware, we always want to do something like

store.setState((state) => { state.x = 3})

and never

store.setState({x: 3})

Is this accurate? I thought the vanilla version was still intended to be supported with immer if you do return a value from your updater or supply a partial state:

  • returning nothing applies any in-place Draft modifications
  • returning a value applies the vanilla shallow merge behavior
  • doing both is an error: caught Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft

This behavior makes sense to me in the context of a middleware: intercepting only the void returns. For example you could use the immer model only for a few operations which operate on nested states, or another example being adding immer to an existing store for a few operations without having to rewrite all of the existing vanilla operations.

I'm no expert on this project so I'll defer to the maintainers, but I think #2495, which explicitly adds TS definitions to support the model described above, contradicts the assumptions in this PR. Hopefully someone with a greater understanding of this library and its middleware can chime in.

@dbritto-dev
Copy link
Collaborator

@connormanning when you use immer you return the whole state so you should merge the current state with the new state you should always replace the state since immer handle the changes, this PR set replace true by default

@dbritto-dev
Copy link
Collaborator

@connormanning let's discuss the changes to immer middleware here

@dai-shi
Copy link
Member

dai-shi commented Apr 19, 2024

oookay, I guess we have some design conflicts.
I guess the question if whether we should force replace=true with immer middleware.

As I generally prefer simpler api and implementation, my suggestion is:

        setState(
          nextStateOrUpdater: (state: Draft<T>) => T | void,
          ...a: SkipTwo<A>
        ): Sr

But, this is a breaking change, so it should only come in v5.

And, my suggestion is, if this does't cover some use cases, use produce without the middleware. (we need to educate it in docs.)

@dai-shi
Copy link
Member

dai-shi commented Apr 19, 2024

Or, maybe:

        setState(
          nextStateOrUpdater: T | ((state: Draft<T>) => T | void),
          ...a: SkipTwo<A>
        ): Sr

@dbritto-dev
Copy link
Collaborator

@dai-shi yeah, I guess we need to implement this on v5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants