Home > Enterprise >  How to map value type to keys of react state in reducer's function parameters?
How to map value type to keys of react state in reducer's function parameters?

Time:12-29

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 })

Playground

  • Related