Custom React hook form validation with yup & material ui
Published: 2020-06-29 11:06:47

Doing any freelancing? want to finish the task quickly? Just follow below, apply on any forms and finsh form validation quickly.

Create a reducer

```
const reducer = (state, action) => {
  switch (action.type) {
    case 'SET_VALUE':
      return {
        ...state,
        [FORMDATA]: action.payLoad,
      };
    case 'SET_ERROR':
      return {
        ...state,
        [ERRORS]: action.payLoad,
      };
    case 'ON_CHANGE':
      return {
        ...state,
        ...action.payLoad,
      };
    default:
      return state;
  }
};
```

Use the below custom hook 

```

export const useForm = (schema, defaultFormValues = {}) => {
  const [state, dispatch] = useReducer(reducer, { [FORMDATA]: defaultFormValues, [ERRORS]: {} });
  const { formData, errors } = state;

  // Commit state result
  const dispatchValue = (field, value, result) => {
    if (result instanceof Yup.ValidationError) {
      // Error
      errors[field] = result.message;
    } else {
      // Delete current state result
      delete errors[field];
    }

    // Update state, for object update, need a clone
    const newState = { [ERRORS]: errors, [FORMDATA]: { ...formData, [field]: value } };
    dispatch({ type: 'ON_CHANGE', payLoad: newState });
  };

  const updateFormValue = (field, value) => {
    // Validate the field, then before catch, if catch before then, both will be triggered

    Yup.reach(schema(), field)
      .validate(value)
      .then((result) => {
        dispatchValue(field, value, result);
      })
      .catch((result) => {
        dispatchValue(field, value, result);
      });
  };

  return {
    validate: async () => {
      const validationResult = { isValid: true, errors: {} };
      try {
        await schema().validate(formData, {
          strict: true,
          abortEarly: false,
          stripUnknown: false,
        });
      } catch (e) {
        const formValidationError = {};
        if (e instanceof Yup.ValidationError) {
          // eslint-disable-next-line no-restricted-syntax
          for (const error of e.inner) {
            // Only show the first error of the field
            if (!formValidationError[error.path]) {
              formValidationError[error.path] = error.message;
            }
          }
        }
        dispatch({ type: 'SET_ERROR', payLoad: formValidationError });
        validationResult.isValid = false;
        validationResult.errors = formValidationError;
        return validationResult;
      }

      return validationResult;
    },
    setValue: (formInput) => {
      //  In case where we need to set all the form values
      if (Array.isArray(formInput)) {
        const [defaultObj] = formInput;
        dispatch({ type: 'SET_VALUE', payLoad: { ...formData, ...defaultObj } });
      } else {
        const { name, value } = formInput;
        updateFormValue(name, value);
      }
    },
    errors: (field) => errors[field],
    getValues: () => formData,
    value: (field) => state[field],
    onFormBlur: (name, value) => {
      updateFormValue(name, value);
    },
  };
};

```

To handle form value and validation just import the above hook.

```

  const { validate, setValue, errors, getValues } = useForm(validateSchema, formData);

```

You can use yup to define the schema

```
import * as yup from 'yup';

// eslint-disable-next-line import/prefer-default-export
export const validateSchema = () =>
  yup.object().shape({
    service: yup
      .number()
      .required('Please provide the service')
  });


```

To get the form values you can use the method exported from useFrom hook like below.

```
  const initialState = getValues();

```

If you are using material-ui then use like below.

```
  <TextField
	id={ROLE}
	name={ROLE}
	label="Role"
	placeholder=""
	maxLength="15"
	fullWidth
	required
	margin="dense"
	value={initialState[ROLE]}
	autoComplete="off"
	disabled={isLoading || isSubmiting || !initialState[SERVICE]}
	onChange={onChangeInput(ROLE)}
	error={!!errors(ROLE)}
	helperText={errors(ROLE)}
	InputLabelProps={{
	  shrink: true,
	  'aria-label': 'Select Role',
	}}
  />
```

If you have any suggetions add comments.... happy coding!!