-
-
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
Fix Immer type inference for setState
#2696
Fix Immer type inference for setState
#2696
Conversation
… `setState` instead of `getState`
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
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. |
setState
type off of store…setState
src/middleware/immer.ts
Outdated
shouldReplace: true, | ||
...a: SkipTwo<A2> | ||
): Sr2 | ||
? SetState extends StoreApi<infer T>['setState'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this works because StoreApi['setState']
is very complicated type. Also, setState
can be modified by upstream middleware. Can we infer T
from A1
and A2
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does infer correctly as the StoreApi setState
type is based off of the single generic passed into StoreApi. I think doing it this way bottles the complexity of the type and handles it appropriately. All the tests passed and it seems to resolve correctly.
When I tried to base it off of A1 and A2, I would've needed to fight through the entire intersection type (e.g. T | Partial<T> | ...
) in order to get T. That method feels more brittle than inferring through StoreApi.
Any situations you're thinking of specifically here? Like a modified setState
type from another middleware?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, actually, if I used the function type, there would be no Partial to fight with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like a modified setState type from another middleware?
Yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check out my latest commit and let me know what you think of that. The assumption I made is that A2
will always have a first parameter that is T | (...args: any[]) => any
. So, T
can be found by excluding any function types. That should be more robust.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inferring off of A1
seemed non-trivial given that it is T | Partial<T> | ...
, which I'm not sure if there's a method to derive T
out of that without making further assumptions about the structure of setState
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, interesting. I guess the approach is right though.
…tional component of state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is too tricky and not 100% correct anyway. seems fine for now.
can you add a test please?
The simplest test would probably be to register an example middleware that modifies |
Check out that new test and let me know if it's sufficient. Pollution of the |
Thanks for working on it.
Hmm, but it made me think that it should be safer if the new test is put in a new test file. What do you think? |
I'd be okay with that, but it does make sense alongside other middleware tests. For most of the middleware it may be nice to have some contrived third party middleware examples to try things against. Outside the scope of this PR but thought I would mention. |
type StoreModifyAllButSetState<S, A> = S extends { | ||
getState: () => infer T | ||
} | ||
? Omit<StoreApi<T & A>, 'setState'> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if I understand this fully, but omitting setState may prevent inferring type from setState, no?
The reason the immer impl works is it's using set
instead of setState
, but we don't get types from set
.
I'm a bit confused what's going on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, just updated that. The goal is to use an example middleware that modifies the store state to add some A
type to all but the setState
, and then assert that it affected the types appropriately on both the inside of the Immer middleware and the outside.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, but still it doesn't feel like a good example, because the implementation of the example middleware should include delete api.setState
, but immer
middleware infers types from setState
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would the implementation need to include delete api.setState
? The example rewrites setState
, it doesn't delete it.
Do you agree that the type assertions are sufficient? Since this change is strictly on a type level, I didn't think implementation was necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it doesn't delete it.
Okay, I think I misread something then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I think I forgot that Write
is an intersection.
Never mind.
1540bfd
to
1067033
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Thanks for your contribution!
(Now, I wonder if we should cut an rc release only for this change. 🤔 )
A minor bump may not hurt 😁 |
Bases the type of
setState
off ofsetState
instead ofgetState
.Related Bug Reports or Discussions
Fixes #1621
Summary
This PR fixes the Immer middleware so that the type of
setState
is inferred off of the store'ssetState
as opposed togetState
. When using a middleware like zustand-computed, this would previously have the result of a differinggetState
generic would move over tosetState
unintentionally.Check List
pnpm run prettier
for formatting code and docs