Home > Enterprise >  TypeScript: Problem typing a custom Form hook
TypeScript: Problem typing a custom Form hook

Time:09-11

I know this is gonna be messy and terrible but while I have the TypeScript basics down, I'm having trouble typing the more advanced concepts, particularly typing useReducer hooks. Here's my code with what I have so far, but I understand I have probably mistyped some items.

import { useCallback, useReducer } from 'react';

interface Inputs {
  id: string;
  value: string;
  inputIsValid: boolean;
}

interface FormState {
  inputs: Inputs[];
  isValid: boolean;
}

interface FormAction {
  inputs: Inputs;
  type: string;
  value: string;
  inputId: string;
  isValid: boolean;
  formIsValid: boolean;
}

const formReducer = (state: FormState, action: FormAction): FormState => {
  switch (action.type) {
    case 'INPUT_CHANGE':
      let formIsValid = true;
      for (const inputId in state.inputs) {
        if (!state.inputs[inputId]) continue;
        if (inputId === action.inputId)
          formIsValid = formIsValid && action.isValid;
        else formIsValid = formIsValid && state.inputs[inputId].inputIsValid;
      }
      return {
        ...state,
        inputs: {
          ...state.inputs,
          [action.inputId]: { value: action.value, isValid: action.isValid },
        },
        isValid: formIsValid,
      };
    case 'SET_DATA':
      return {
        inputs: action.inputs,
        isValid: action.formIsValid,
      };
    default:
      return state;
  }
};

export const useForm = (
  initialInputs: Inputs,
  initialFormValidity: boolean
) => {
  const [formState, dispatch] = useReducer(formReducer, {
    inputs: initialInputs,
    isValid: initialFormValidity,
  });
  const inputHandler = useCallback(
    (id: string, value: string, isValid: boolean) => {
      dispatch({
        type: 'INPUT_CHANGE',
        value: value,
        isValid: isValid,
        inputId: id,
      });
    },
    []
  );
  const setFormData = useCallback(
    (inputData, formValidity: boolean) => {
      dispatch({
        type: 'SET_DATA',
        inputs: inputData,
        formIsValid: formValidity,
      });
    },
    []
  );
  return [formState, inputHandler, setFormData];
};

I also have a custom Input component that uses the formHook. This is all used from following Max Schwarzmuellers MERN stack course, which is super helpful but doesn't include typing. I've tried following his typescript course but haven't finished it yet. My main problems come with the useReducer functions, which are overloaded with issues. Which look something like this:

No overload matches this call.
  Overload 1 of 5, '(reducer: ReducerWithoutAction<any>, initializerArg: any, initializer?: undefined): [any, DispatchWithoutAction]', gave the following error.
    Argument of type '(state: FormState, action: FormAction) => FormState' is not assignable to parameter of type 'ReducerWithoutAction<any>'.
  Overload 2 of 5, '(reducer: (state: FormState, action: FormAction) => FormState, initialState: FormState, initializer?: undefined): [FormState, Dispatch<...>]', gave the following error.
    Type 'Inputs' is not assignable to type 'Inputs[]'.ts(2769)
form-hook.tsx(10, 3): The expected type comes from property 'inputs' which is declared here on type 'FormState'

Edit: Changed Inputs to Inputs[] and changed FormState to

type FormAction =
  | { type: 'INPUT_CHANGE'; value: string; inputId: string; isValid: boolean }
  | { type: 'SET_DATA'; formIsValid: boolean; inputs: Inputs[] };

Courtesy of https://www.youtube.com/watch?v=9KzQ9xFSAEU which I highly recommend to anyone trying to understand using useReducer with TypeScript Also thank you to everyone else who contributed, this was definitely a great learning experience!

CodePudding user response:

The problem is initialInputs is typed to Inputs when it should be Inputs[]. So you need to re-type that arg on useForm.

export const useForm = (
  initialInputs: Inputs[],
  initialFormValidity: boolean
) => {
  const [formState, dispatch] = useReducer(formReducer, {
    inputs: initialInputs,
    isValid: initialFormValidity
  });

Probably this needs changing in the action type as well? The change probably will lead you to more changes, but the above should fix the error you were seeing and uncover the rest.

  • Related