I'm working on a state manager library for React, I wanted to add a helper function which would allow users to set deeply nested states easily from outside of React with the use of a selector and so far the helper function looks something like this:
type NestedStateSetter<State> = <Selected = State>(
update: Selected | (() => Selected) | (() => Promise<Selected>),
selector?: (state: State) => Selected
) => Promise<Selected>;
const initialState = {
a: {
b: {
c: 0,
},
},
};
const setState: NestedStateSetter<typeof initialState> = (update, selector) => {
throw "Yeet"
};
setState(1, (state) => state.a.b.c) // Ok
setState(() => 2, (state) => state.a.b.c) // Ok
setState(async () => 5, (state) => state.a.b.c) // Not ok
But the third call to 'setState' is giving us this error on the (state) => state.a.b.c
of the third call above:
Argument of type '(state: { a: { b: { c: number; }; }; }) => number' is not assignable to parameter of type '(state: { a: { b: { c: number; }; }; }) => Promise'. Type 'number' is not assignable to type 'Promise'.(2345)
TypeScript Playground link
Stackblitz link
I tried to use a curried function but that would be a breaking change and I would rather avoid it as much as possible.
You need to order the types in your union from most specific to least specific to prevent TypeScript from matching the first, most generic type.
So, instead of:
update: Selected | (() => Selected) | (() => Promise<Selected>)
Use:
update: (() => Promise<Selected>) | (() => Selected) | Selected
Complete snippet:
type NestedStateSetter<State> = <Selected = State>(
update: (() => Promise<Selected>) | (() => Selected) | Selected,
selector?: (state: State) => Selected
) => Promise<Selected>;
const initialState = {
a: {
b: {
c: 0,
},
},
};
const setState: NestedStateSetter<typeof initialState> = (update, selector) => {
throw 'Yeet';
};
setState(1, (state) => state.a.b.c); // Ok
setState(
() => 2,
(state) => state.a.b.c
); // Ok
setState(
async () => 5,
(state) => state.a.b.c
); // Ok