0

So im trying to create my own custom hook to handle all my forms. For that im using the useReducer hook to handle al input changes and save them in the state.

The handleChange method recives two arguments (inputName, inputValue) and should update the value (inputValue) of the corresponding property (inputName) in the store trough the INPUT_CHANGE action. However Typescript doesn't infer the propertie name in field property of the payload of such action.

Reducer

export enum ActionTypes {
  INPUT_CHANGE,
  CLEAR,
}

type InputChangeAction<T extends object, K extends keyof T> = {
  type: ActionTypes.INPUT_CHANGE;
  payload: {
    /* if T is {title: string, description?: string} 
       Then K should be "title" | "description" */
    field: K;
    value: string;
  };
};

type ClearAction = {
  type: ActionTypes.CLEAR;
};

type ReducerAction<T extends object, K extends keyof T> = ClearAction | InputChangeAction<T, K>;

export const FormHanldeReducer= <T extends object, K extends keyof T>(
  state: T,
  action: ReducerAction<T, K>,
) => {
  switch (action.type) {
    case ActionTypes.INPUT_CHANGE:
      const {field, value} = action.payload;
      return {...state, [field]: value};

    case ActionTypes.CLEAR:
      return (Object.keys(state) as Array<keyof typeof state>).reduce((newState, oldStateKey) => {
        newState[oldStateKey] = '' as T[K];
        return newState;
      }, {} as typeof state);

    default:
      return state;
  }
};

Hook

import {useReducer} from 'react';
import {useAppDispatch} from '../../store/hooks';
import {ActionTypes, FormHanldeReducer} from './FormHandleReducer';

export const useFormHandle = <T extends object, K extends keyof T>(initialState: T) => {
  //redux hooks
  const storeDispatch = useAppDispatch();

  //state
  const [formState, localDispatch] = useReducer(
    FormHanldeReducer,
    initialState,
  );

  //methods
  const handleChange = (
    inputName: keyof T,
    inputValue: string,
  ) => {
    localDispatch({
      type: ActionTypes.INPUT_CHANGE,
      payload: {
        field: inputName, //Type 'string | number | symbol' is not assignable to type 'never'.
        value: inputValue,
      },
    });
  };

  const save = () => {
    //storeDispatch(actionToBeDespatched());
  };

  const dismiss = () => {
    localDispatch({type: ActionTypes.CLEAR});
  };

  return {
    formState,
    handleChange,
    save,
    dismiss,
  };
};

For some reason the field prop is never when should be a union of string literals

I feel I'm very close to the solution but i already browse so many pages. What can I do to solve this problem?

Nahuel M.
  • 146
  • 8
  • 2
    I'm too lazy to write up an answer but I fixed it filling out the generics on line 50 https://tsplay.dev/mArVPW `const [formState, localDispatch] = useReducer<(state: T, action: ReducerAction) => T>(`. You need to apply the `T` from your hook as the `T` of the reducer. Otherwise it is not the same `T` and the reducer loses its generic-ness. – Linda Paiste Oct 07 '22 at 03:35
  • Awesome, that fixed it. Thanks a lot. However I would ask you for a detailed explanation if it's not a problem and if there is a less-code solution may be usin the react built-in types for reducers. Whatever you want, thanks again. – Nahuel M. Oct 07 '22 at 14:33
  • 1
    Here's an answer that I wrote to a similar question: https://stackoverflow.com/a/68875462/10431574 With the react built-in `Reducer` type it would be `useReducer>>(` – Linda Paiste Oct 07 '22 at 17:08

0 Answers0