Say I have an initial state like this:
const initialState = {
hasError: false,
counter: 0,
token: [...Array(2)].map(() => ''),
};
and a reducer function, that consumes and updates the state, like in the offical example (modified to include action.value
instead of state.count
):
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: action.value 1};
case 'decrement':
return {count: action.value - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement', value: 1})}>-</button>
<button onClick={() => dispatch({type: 'increment', value: -1})}> </button>
</>
);
}
How can I make this function resolve the type of action.value
from action.type
?
For example:
const reducer = <K extends keyof typeof initialState>(state: typeof initialState, action: { type: K; value: typeof initialState[K] }): typeof initialState => {
switch (action.type) {
case 'hasError': {
return {
...state,
hasError: action.value, // here the type should be `boolean`
};
}
case 'token': {
return {
...state,
token: action.value, // type should be `string[]`
}
// etc...
}
};
I also tried something like this, to no avail:
type TypeResolver<K> = Extract<TypeMap[keyof TypeMap], { state: K }>['valueType'];
type TypeObject<K extends keyof typeof initialState> = { state: K, valueType: typeof initialState[K] };
type TypeMap = {
error: TypeObject<'hasError'>;
resend: TypeObject<'resendEnabled'>;
timerIteration: TypeObject<'timerIteration'>;
};
const reducer = <K extends keyof TypeMap>(state: typeof initialState, action: { type: K; value: TypeResolver<K> }): typeof initialState => {
//...
}
CodePudding user response:
It is currently not possible to narrow the type of action.value
by checking the value of action.type
because they are both generic types.
We can rewrite the function signature in a way that makes the action
parameter a discriminated union which can be narrowed.
const reducer = (
state: typeof initialState,
action: {
[K in keyof typeof initialState]: { type: K, value: typeof initialState[K] }
}[keyof typeof initialState]
): typeof initialState => {
switch (action.type) {
case 'hasError': {
return {
...state,
hasError: action.value, // here the type should be `boolean`
};
}
case 'token': {
return {
...state,
token: action.value, // type should be `string[]`
}
}
default: {
return state
}
}
}
This would not change the way you are calling the reducer function.
reducer(initialState, { type: "hasError", value: true })