shaifarfan08@gmail.com

useForm React Hook - Simplify Form Handling in React

📆 Aug 20, 2024

react.js hooks custom-hook
useForm React Hook - Simplify Form Handling in React

🕝 8 min read

📝 Edit on GitHub

Introduction

Handling forms in React can be a tedious task. That’s why there are libraries like Formik, React Hook Form, etc., to simplify the process, and they do the job pretty well. But if you are working on a small project or don’t want to add a library for form handling, you can easily create a custom hook to handle forms. That’s exactly what we’ll do today.

We’re going to keep things simple and easy to use. Also, we need to make sure it works for all kinds of input fields whether it’s text, textarea, select, checkbox, radio, and more.

The key idea is that we’ll pass the initial values of the form to the hook, and it will return the values & some functions to handle the form.

Our hook will include three main functions:

  • getInputProps: Creates common input field properties
  • handleFieldChange: Manages changes to input values (onChange event handler)
  • resetForm: Resets the form to its initial values

Let’s dive in!

Logo

Github Repo Example

I have created a Github repo where you will find the example code for this blog.

https://github.com/ShaifArfan/useForm-hook-example

Logo

YouTube Video Tutorial

I have also created a YouTube video tutorial on this blog. Check it out if you prefer video content.

https://youtu.be/4vqdLl_uzDU

Set Up the Hook

If you’re new to React hooks, they’re functions that let you reuse stateful logic across your components. Let’s start by creating one for handling forms. React hooks should always start with use, so we’ll name our custom hook useForm.

First, let’s create a custom hook called useForm in a new file named useForm.ts. Personally, I like to keep my hooks organized in a separate folder called hooks.

└── src
    ├── components
    │   └── Form.tsx
    └── hooks
        └── useForm.ts

So, let’s create a default export function called useForm that takes in the initial values. Since we don’t know what those values will be, we’ll make it a generic type T. This way, it can be any type and will match the type of the initial values passed to it.

// src/hooks/useForm.ts

import { useState } from 'react';

export default function useForm<T>(initialValues: T) {}

Now, we need to store the form values in state. We’ll use React’s built-in useState hook, initializing it with the initialValues to set up the initial state.

// src/hooks/useForm.ts

import { useState } from 'react';

export default function useForm<T>(initialValues: T) {
  const [values, setValues] = useState(initialValues); 
}

Reset Form Function

Let’s start with creating the reset function that will reset the form to its initial values. To do this, we can simply call setValues with the initialValues.

// src/hooks/useForm.ts
import { useState } from 'react';

export default function useForm<T>(initialValues: T) {
  const [values, setValues] = useState(initialValues);

  const resetForm = () => { 
    setValues(initialValues); 
  }; 
}

Form Field Change Handler

Now for the fun part, let’s create the form field change handler function inside the hook. This function will take the change event as an argument and update the form values based on the field name.

const handleFieldChange = (
  e: React.ChangeEvent<
    HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
  >
) => {
  const { name, value, type } = e.target;
  let v: unknown = value;

  if (type === 'checkbox') {
    v = (e.target as HTMLInputElement).checked;
  }
  // handle other input types here if needed

  setValues({
    ...values,
    [name]: v,
  });
};

As you can see, we’re taking the event as an argument, and we need to ensure it supports all form field elements. That’s why we’re using React.ChangeEvent along with other element type like HTMLInputElement, HTMLTextAreaElement, and HTMLSelectElement. From the event, we can then extract the field name, value, and type.

Next, let’s create a temporary variable v to store the value. Depending on the field type, we might need to adjust the value. For example, for a checkbox, the value comes from the checked property. Similarly, you can handle other input types as needed.

Finally, we need to update the form values using the setValues function. We’ll spread the existing values and then update the field value based on the field’s name.

Input Props Generator

On form fields, we often have many attributes like name, value, type, checked, onChange, etc. Repeating these over and over can be tedious. To simplify this, we’ll use getInputProps to generate shared attributes for the fields. This will keep our code cleaner and more maintainable.

In this function, we will take the field name, type and value as arguments. The name should match the key in the initial values object, while the value is optional and primarily needed for radio fields. The type refers to the form field type, such as text, number, checkbox, radio etc.

First, We will create a temporary variable payload to store the field props. The payload will have the following properties:

  • name: The name of the field. It’s value will be the same as the argument passed to the function.
  • value (optional): The value of the field.
  • onChange: onChange event of the field. We will use the handleFieldChange function here.
  • type: The type of the field. It’s value will be the same as the argument passed to the function.
  • checked (optional): The checked state of the checkbox or radio field. We will set it’s value based on the value of the field.

We’ll start by adding the name, onChange, and type properties to the payload object. Then, depending on the type, we’ll include other properties like value and checked. Finally, we’ll return the payload object, which can be spread across the form field to apply these attributes easily.

const getFieldProps = (name: keyof T, type = 'text', value?: string) => {
  const payload: {
    name: keyof T;
    value?: string | number;
    onChange: typeof handleFieldChange;
    type: string;
    checked?: boolean;
  } = {
    name,
    onChange: handleFieldChange,
    type,
  };

  if (type === 'checkbox') {
    payload.checked = values[name] as boolean;
  } else if (type === 'radio') {
    if (!value) throw new Error('Radio button value is required');
    payload.checked = values[name] === value;
    payload.value = value;
  } else {
    payload.value = value || (values[name] as string | number);
  }

  return payload;
};

Final Hook Code

Finally, the last thing we need to do is return the values, initialValues, handleFieldChange function, resetForm function, and getFieldProps function from the hook.

The final code of the useForm hook will look like this: 👇

// src/hooks/useForm.ts
import { useState } from 'react';

export default function useForm<T>(initialValues: T) {
  const [values, setValues] = useState(initialValues);

  const resetForm = () => {
    setValues(initialValues);
  };

  const handleFieldChange = (
    e: React.ChangeEvent<
      HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
    >
  ) => {
    const { name, value, type } = e.target;
    let v: unknown = value;

    if (type === 'checkbox') {
      v = (e.target as HTMLInputElement).checked;
    }
    // handle other input types here if needed

    setValues({
      ...values,
      [name]: v,
    });
  };

  const getFieldProps = (name: keyof T, type = 'input', value?: string) => {
    const payload: {
      name: keyof T;
      value?: string | number;
      onChange: typeof handleFieldChange;
      type: string;
      checked?: boolean;
    } = {
      name,
      onChange: handleFieldChange,
      type,
    };

    if (type === 'checkbox') {
      payload.checked = values[name] as boolean;
    } else if (type === 'radio') {
      if (!value) throw new Error('Radio button value is required');
      payload.checked = values[name] === value;
      payload.value = value;
    } else {
      payload.value = value || (values[name] as string | number);
    }

    return payload;
  };

  return {
    initialValues,
    handleFieldChange,
    resetForm,
    values,
    getFieldProps,
  };
}

Usage in a Component

That’s it! We’ve created a custom hook to handle forms in React.js. Now, let’s see how to use this hook in a component.

Here’s an example of how to use the useForm hook in a component:

import useForm from './hooks/useForm';

function Form() {
  const { values, handleFieldChange, resetForm, getFieldProps } = useForm({
    name: 'initial name',
    myNumber: 45,
    email: 'test@example.com',
    agreement: true,
    myRange: 200,
    myRadio: 'option2',
    mySelect: 'option2',
    message: '',
  });

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log('Form submitted');
    console.log(values);
  };

  return (
    <div className="form">
      <h1>UseForm Hook:</h1>
      <form onSubmit={handleSubmit}>
        <div className="flex flex-col">
          <div className="form__field">
            <label htmlFor="name">Name:</label>
            <input id="name" {...getFieldProps('name', 'text')} />
          </div>
          <div className="form__field">
            <label htmlFor="myNumber">My Number:</label>
            <input id="myNumber" {...getFieldProps('myNumber', 'number')} />
          </div>
          <div className="form__field">
            <label htmlFor="email">Email:</label>
            <input id="email" {...getFieldProps('email', 'email')} />
          </div>
          <div className="form__field form__field--checkbox">
            <input id="agreement" {...getFieldProps('agreement', 'checkbox')} />
            <label htmlFor="agreement">Agree:</label>
          </div>
          <div className="form__field">
            <label htmlFor="myRange">My Range: {values.myRange}</label>
            <input
              id="myRange"
              min={100}
              max={1000}
              step={100}
              {...getFieldProps('myRange', 'range')}
            />
          </div>
          <div className="form__field form__field--radio">
            <div>
              <input
                id="option1"
                {...getFieldProps('myRadio', 'radio', 'option1')}
              />
              <label htmlFor="option1">Option1</label>
            </div>
            <div>
              <input
                id="option2"
                {...getFieldProps('myRadio', 'radio', 'option2')}
              />
              <label htmlFor="option2">Option2</label>
            </div>
            <div>
              <input
                id="option3"
                {...getFieldProps('myRadio', 'radio', 'option3')}
              />
              <label htmlFor="option3">Option3</label>
            </div>
          </div>
          <div className="form__field">
            <label htmlFor="mySelect">My Select</label>
            <select id="mySelect" {...getFieldProps('mySelect', 'select')}>
              <option value="option1">Option1</option>
              <option value="option2">Option2</option>
              <option value="option3">Option3</option>
            </select>
          </div>
          <div className="form__field">
            <label htmlFor="message">Message:</label>
            <textarea
              id="message"
              name="message"
              onChange={handleFieldChange}
            ></textarea>
          </div>
          <div className="buttons">
            <button type="submit" className="submit">
              Send
            </button>
            <button type="button" onClick={resetForm}>
              Reset
            </button>
          </div>
        </div>
      </form>
    </div>
  );
}

In this example, we import the useForm hook and initialize it with the form’s initial values. We then extract values, getInputProps and resetForm from the hook. Then we just need to spread the getInputProps function’s return values to the input fields. Finally, we use the resetForm function to reset the form to its initial values when the reset button is clicked..

Now, you can easily handle forms in React. Although this example is simple, you can extend it to manage more complex forms. You can also add validation, error handling, and other features as needed. If you require more advanced functionality, you can always use to third-party libraries. But for simple forms, this custom hook will be enough. It will keep your code clean and easy to maintain.

Shaif Arfanshaifarfan08@gmail.com

© shaifarfan.com 2024. All rights reserved

privacy policy 🔒