-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
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 replaceReducer with a store enhancer #3524
fix replaceReducer with a store enhancer #3524
Conversation
6c43c07
to
e092c6f
Compare
…ionality, for instance
e092c6f
to
b95330c
Compare
index.d.ts
Outdated
* assumption is that root reducers that only return a basic type (string, number, null, symbol) probably won't | ||
* be using replaceReducer anyways. | ||
*/ | ||
export type BaseType<S> = S extends {} ? {} : S |
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.
The current name isn't descriptive. This should be called EnsureObject
.
I don't agree with the approach here. It is totally valid to return a non-object from the reducer and to replace it with another reducer that does the same. We need to support that case, otherwise this is incorrectly typed. |
The basic approach of the PR is to explicitly pass in state and store extension to the definition of Store so that we can access them in replaceReducer. Is that what you disagree with? Or just the implementation detail of the limitation on what state extension can be? If it is the limitation on State extension, the obvious solution is to remove the extends clause altogether, and also |
Yeah, my problem isn't with the state/store extensions, just the the objectification of the StateExt. |
ok, let me see what I can wrangle |
@@ -51,7 +51,7 @@ export default function applyMiddleware<Ext, S = any>( | |||
): StoreEnhancer<{ dispatch: Ext }> | |||
export default function applyMiddleware( | |||
...middlewares: Middleware[] | |||
): StoreEnhancer { | |||
): StoreEnhancer<any> { |
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 fix ends up being fine because the overloads provide more restrictive typing (you can check in tests/typescript/middleware.ts
to see that the stores returned are strictly typed)
OK, here is what I came up with. Instead of trying to get a base type that we can always extend the state with, the state is only extended when there is an extension present. I did this by changing the default value of export type ExtendState<State, Extension> = [Extension] extends [never]
? State
: State & Extension This works because a tuple with |
StateExt = never, | ||
Ext = {} |
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.
Hey, would it be possible to make StateExt
and Ext
optional whenever not using an Enhancer?
At the moment createStore<S,A,StateExt,Ext>
generic typings are required as opposed as createStore<S,A>
for stores without enhancers. Thanks!
* fix replaceReducer with a store enhancer * remove erroneous restriction on StateExt * remove the other extension - our store enhancer might add array functionality, for instance * add reasonable defaults for Ext and StateExt * fix state, add a test for non-object-based state * add verification that store extension is also passed to replaceReducer * better fix: set state default based on what base type it is * fix array test * fix typing of StateExt * add mhelmerson example * fix replaceReducer, so that it infers types, fix example test * fix the weird type hacks in the test * add final working example * update based on PR type changes * fix type * update tests to reflect complete examples * merge the changes from index.d.ts into types/store.ts * extend store type * much better approach: only extend the state when we have an extension * fix typing issues not caught before * add link to the place I learned about this Former-commit-id: 5e5ac68 Former-commit-id: 6de326a
* fix replaceReducer with a store enhancer * remove erroneous restriction on StateExt * remove the other extension - our store enhancer might add array functionality, for instance * add reasonable defaults for Ext and StateExt * fix state, add a test for non-object-based state * add verification that store extension is also passed to replaceReducer * better fix: set state default based on what base type it is * fix array test * fix typing of StateExt * add mhelmerson example * fix replaceReducer, so that it infers types, fix example test * fix the weird type hacks in the test * add final working example * update based on PR type changes * fix type * update tests to reflect complete examples * merge the changes from index.d.ts into types/store.ts * extend store type * much better approach: only extend the state when we have an extension * fix typing issues not caught before * add link to the place I learned about this Former-commit-id: 5e5ac68 Former-commit-id: 6de326a
This fixes #3482 (replaceReducer typing fails for store enhancers that update the state or extend the store) without requiring any change to the use of existing types (fully typing backwards compatible).
It adds 2 new templates to the generic for defining a
Store
, the extension to state, and the extension to the store definition.By adding these 2 template parameters, we can mix them with the new reducer state from
replaceReducer
:and return a store that has the correct typings for both state and the store extension.
To enable this functionality for store enhancers, it requires these 3 related changes:
createStore()
needs to be augmented to pass these values to the return typeStore
. Since this is an overloaded function, theStoreCreator
interface that definescreateStore
is updated for both of the overloads. See the note below about the default value forStateExt
andExt
.StoreEnhancer
type needs to re-define the firstcreateStore
parameter passed to it. It now uses, and thus requires theStateExt
andExt
, otherwise it expects the returned store not to have these properties. The point of a store enhancer is to return the enhanced store fromcreateStore
. The only reason this didn't fail in the past was because we didn't pass the store enhancer extension values tocreateStore
.Store
fromStoreEnhancer
needs to have theStateExt
andExt
extensions passed to it to enable the fix inreplaceReducer
.One other crucial note:
By default, the.StateExt
is defined to be the initial state. Originally, the PR had{}
as the default value forStateExt
, but this will fail if the state is defined as an array, or any other non-object value. By usingS
, this allows the default case to work for all possible values of the state shapeExt
is defined as{}
because the store definition mixed with{}
cancels out the{}
.I also added a test for store enhancer with
replaceReducer
, to verify correctness of the new typing, and a test for a non-object store value to thestore
test.UPDATE:
It turns out using
StateExt = S
causesreplaceReducer
to fail. Instead, I changed it to check the base type. If it extends an object,{}
will be a no-op. Otherwise, we return the typeS
, which will be one of the primitive types. There is no easy way to guarantee the type won't interfere withreplaceReducer
, but the assumption I am making is that if someone's root reducer isn't returning an object, it probably isn't going to callreplaceReducer
either.