-
Notifications
You must be signed in to change notification settings - Fork 7
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
TypeScript: Store values should probably all infer as optional #95
Comments
@cboon-jeff thanks for reporting! I hadn't considered this before, I'll have to see why this is never causing problems for us. I agree that this behavior is not sufficiently type-safe. I think any solution here would be a breaking change. I can tell already that making all state fields undefinable would be too disruptive a change. I'm in favor of making all undefined values passed to This would be a breaking change in this scenario: const store = injectStore<{ foo?: number }>({ foo: 1 });
expect(store.getState().foo).toBe(1)
store.setStateDeep({ foo: undefined })
expect(store.getState().foo).toBeUndefined() // passes currently, would fail after this change Changing the behavior of WorkaroundsI'm fine recommending that you use Alternatively, if you want improved store methods now and don't mind maintaining some code for it, here's an undocumented pattern that we've started using in several places: Extend the Store class and create your own injector that injects instances of it. Here's an example that overrides the base Store class's class CustomStore<State> extends Store<State> {
setStateDeep(settable: Settable<RecursivePartial<State>, State>, meta?: any) {
const newState =
typeof settable === 'function'
? (settable as (state: State) => RecursivePartial<State>)(this.getState())
: settable;
const filteredState =
newState && typeof newState === 'object'
? (_.omitBy(newState, _.isUndefined) as RecursivePartial<State>)
: newState;
return super.setStateDeep(filteredState, meta);
}
}
export const injectCustomStore = <State>(initialState: State, subscribe = true) => {
const store = injectMemo(() => new CustomStore<State>(null, initialState), []);
const self = injectSelf();
injectEffect(
() => {
if (!subscribe) return;
const subscription = store.subscribe(() => self.invalidate());
return () => subscription.unsubscribe();
},
[subscribe],
{ synchronous: true }
);
return store;
}; I will officially document this pattern soon. No changes are planned for this, though note that it has a known TS issue when the store's state is an array. I'll release the fix for that before documenting this pattern. |
Thanks for the fast and detailed response. This all sounds great, and thanks for giving us multiple solutions whilst we wait on a v2 implementation. Really appreciate it. |
Zedux Version
1.2.0
Description
If I have a Zedux store that has an object structure and I either pass in a type for the store as a generic or Zedux infers it, there is inadequate type-safety to prevent values getting unset. This happens because setStateDeep needs to be able to have any key be optional. So I can currently use this to set what should be a required field as undefined:
store.setStateDeep({someRequiredField: undefined});
. This will actually set the value to be undefined.So I would assume that either all values for the store should be possibly undefined, or setStateDeep should ignore undefined values when merging. Below is a contrived example, in our case it was happening because we were getting one value from somewhere else and using it in setStateDeep.
Reproduction
So above I would either expect an error in TS on this line:
console.log(errorsIfUndefined(status));
as I would have expected the inferred type of the store to essentially be a Partial<>, or thatstore.setStateDeep({ saveStatus: undefined });
is basically a no-op. I was thinking the former, but it does have the downside of always having to do extra checks that the value exists even if you aren't using setStateDeep anywhere. Could be better DX for Typescript users to prevent setting undefined in setStateDeep.The text was updated successfully, but these errors were encountered: