DEV Community

Cover image for Custom Form Hook in React
Daniel Troyano
Daniel Troyano

Posted on

Custom Form Hook in React

React Hooks give you handy ways to manage state and let you compartmentalize your code in some fascinating ways. So let's take a look at one example of that by making a custom hook that manages a form.

There are a few rules to remember when you are making a custom hook, which are given to us by the React Docs.

  • Don't call Hooks inside loops, conditions, or nested functions. They should only be called from the top level of your React function.
  • Don't call Hooks from inside regular Javascript functions. They can only be called from within React functions or custom Hooks.
  • Custom Hooks should begin with "use" so we know they are Hooks.

That's it! Let's get making!


We're going to use the same initial form data and Input component from the previous article in this series, so check there if you want to see them. I'm just going to focus on creating a custom Hook here.

For this simple example we're going to want it to track the state of the form, which is all of the data in the form object, as it changes through user interaction. It will also need a change handler, which will update the state, and it will need a submit handler that, when called, will just return all the values in the state so that we can do something with them.

So let's make our custom hook!

const useForm = (initialValues, onSubmit) => {
  const [state, dispatch] = useReducer(formReducer, initialValues);

  function changeHandler ({target: {value, id}}) {
    const updatedElement = {...state[id]};
    updatedElement.value = value;
    dispatch({id, updatedElement})
  };

  const submitHandler = event => {
    event.preventDefault();
    const results = Object.keys(state).reduce((final, key) => {
      final[key] = state[key].value;
      return final;
    }, {});
    onSubmit(results)
  }

  return {state, submitHandler, changeHandler}
}

//Our custom hook also needs a reducer so I've included a very simple one here
function formReducer (prevState, {id, updatedElement}) {
  return {...prevState, [id]: updatedElement};
};

As you can see, our custom hook takes two parameters, a set of initial values and a callback function that it will call when the form is submitted.

The first thing our custom Hook does is call useReducer. Hooks can call other Hooks, because that's how we're going to manage the state in this Hook. Hooks can have state, which is one of the best things about custom Hooks in React.

Next we set up a change handler, which just gets the current value of the element being changed and its id, which is its key in the state object. The change handler updates the targeted element with the new value. It then calls dispatch so the reducer can update the state.

Then we setup the submit handler, which gets a list of all the values from the state object and puts them into a new object, and then calls the passed in callback function with those values.

And finally we return state, the submit handler, and the change handler so that we can access them in our form component. Now that we've made our custom hook, let's set that component up now!

const form = () => {
  const {state, submitHandler, changeHandler} = useForm(initialForm, values => console.log(values));

  return (
    <div>
      <form onSubmit={submitHandler}>
        {Object.keys(state).map(key => (
          <Input
            changed={changeHandler}
            key={key}
            id={key}
            value={state[key].value}
            label={state[key].label}
          />
        ))}
        <button>Submit</button>
      </form>
    </div>
  );
};

export default form;

As you can see, this looks pretty similar to the last form component. The major difference between our last form component and this one is our useForm Hook and that we're using a submit handler. The useForm is taking in the initial form, just like with our useReducer call before, but now it's also taking a callback function that, in this case, is just going to print whatever it is called with to the console.

More interesting is what useForm is returning. State is all of the up to date form objects, the submit handler is how we submit the form, and the change handler is a function we can just pass down to each Input element, without using a callback function. And each Input element will update correctly because it targets them by their id and then updates the state, which causes them to re-render.

And just like that we have our own custom reusable useForm Hook. It sure looks simple, but we could expand it to include functions to verify form fields, or really whatever we want as the need requires.

I hope I've demystified React Hooks a little, and piqued your interest enough that you'll want to go out and create your own custom React Hooks.

Latest comments (0)