How To Create Custom Hook Using React 18.2.0 (UseReducer)

Introduction

In this article, you will learn how to create a custom hook in React 18.2.0 and call it using input components, and it will validate the data. Once data is validated, it will call the respective handler and update the states. In this article, we use UseReducer for state management.

Why do we require a custom hook?

  1. Custom hooks provide a simple alternative to Higher Order Component and Render props.
  2. Custom hook provides less repetitive code.
  3. Custom hook also provides fewer keystrokes.

Topics Covered

This article demonstrates how to build the following,

  1. Create a custom hook using React 18.2.0.
  2. Create a sample project to consume the custom hook.
  3. Testing

Pre-requisites

  1. Download and install React 18.2.0.

Tools

  1. React 18.2.0

Task 1. Create a sample react-js application

In this task, you will see how to create a sample react-js application using visual studio code.

Step 1

Open visual studio code and run the command in the terminal.

How To Create Custom Hook

Step 2

Once the project is created successfully.

Go inside the folder.

How To Create Custom Hook

And run the project

How To Create Custom Hook

Step 3

To check default project is running successfully. Consider the below screen.

Once the project runs successfully below screen will appear.

How To Create Custom Hook

Task 2. Create a new folder like "CustomHook" and add a component in it 'use-input.js'

In this task, you will see how to create a component inside a folder.

Step 1

Create a new folder inside src => with the name "CustomHook" and a component.

Don't confuse between my project and folder name. That's why my project name is custohook ('m' Missing), and folder name is 'CustomHook'

How To Create Custom Hook

Step 2

Add a component in this folder with the name use-input.js. As per standard, when we create any custom hook, always start's the name using the 'use' keyword.

How To Create Custom Hook

Step 3

Put the below code in this file. In the below code, we accept the validation condition as an input parameter, and the output will be an object in which the component will pass/call two handlers.

    valueChangedHandler,
    inputBlurHandler

Those will call and execute at parent components, events will be 'onChange' and 'onBlur'.

With few properties

    value: enteredValue,
    isValid: valueIsValid,
    hasError,

These properties will be used to validate the parent component controls 'values, and the Last property will be used to reset the default value.

reset
​​​​​​​import { useReducer } from "react";

const initialInputState = {
    value: '',
    isTouched: false
}
const inputStateReducer = (state, action) => {
    if (action.type === 'INPUT') {
        return {
            value: action.value,
            isTouched: state.isTouched
        };
    }
    if (action.type === 'BLUR') {
        return {
            value: state.value,
            isTouched: true
        };
    }
    if (action.type === 'RESET') {
        return {
            value: initialInputState.value,
            isTouched: initialInputState.isTouched
        }
    }
    return initialInputState;
}
export const useInput = (validateValue) => {
    const [inputState, dispatchAction] = useReducer(inputStateReducer, initialInputState);
    const valueIsValid = validateValue(inputState.value);
    const hasError = !validateValue && inputState.isTouched;
    const valueChangedHandler = (event) => {
        dispatchAction({
            type: 'INPUT',
            value: event.target.value
        });
    };
    const inputBlurHandler = () => {
        dispatchAction({
            type: 'BLUR'
        });
    };
    const reset = () => {
        dispatchAction({
            type: 'RESET'
        });
    };
    return {
        value: inputState.value,
        isValid: valueIsValid,
        hasError,
        valueChangedHandler,
        inputBlurHandler,
        reset,
    };
}

Note: Custom control has one input property, and after validation, either success/failure, it will share the response.

Step 4

Create another component with the name "BaseForm".

How To Create Custom Hook

In which the user has two input fields, using a custom component system can validate the input field response and showcase the proper error or success.

Note: I keep two input controls for my example, but it can be as many as you require.

Step 5

Add the below line of code to create the Email and Name input field with an error message, which will show based on the conditions.

​​​​​​​export function BaseForm() {
   const handleSubmit = async (event) => {
    event.preventDefault();
  };

  return (
    <body>
        <h3>Custom Hook</h3>
      <table border='1px solid'>
        <tr>
          <td>Please enter the Email</td>
          <td>
            <input
              id="email"
              type="email"
            ></input>
          </td>
        </tr>
        <tr>
          <td colSpan='2'>
            {!IsEmailValid && (
              <p className="invalidInput">Please enter a valid email</p>
            )}
          </td>
        </tr>
        <tr>
          <td>Please enter the Name</td>
          <td>
            <input
              id="name"
              type="text"
            ></input>
          </td>
        </tr>
        <tr>
          <td colSpan='2'>
            Name can't be empty
          </td>
        </tr>
        <tr>
          <td colSpan="3">
            <button className="submitBtn" onClick={handleSubmit}>
              Submit
            </button>
          </td>
        </tr>
      </table>
    </body>
  );
}

Consume Custom Control

Step 1

Import the custom control in baseform control.

How To Create Custom Hook

Step 2

Modify input control and add handlers that will call when users type any value in it.

How To Create Custom Hook

The name can be anything as your wish

How To Create Custom Hook

Step 3

Consume/call the custom control. The user must validate the same or different logic based on the requirement.

For Name: The user must enter the value, and before sending it to custom control, we are using the Trim function to remove the blank space.

For Email: The user must enter a valid email or not, and custom control will validate it.

How To Create Custom Hook

The complete code will be this.

import { useInput } from "./CustomHook/use-input";

export function BaseForm() {

  const {
    value: enterdName,
    hasError: nameHasError,
    isValid: IsNameValid,
    valueChangedHandler: handleValueChange,
    inputBlurHandler: handleBlueChange,
    reset: handlerReset,
  } = useInput((value) => value.trim() === "");

  const {
    value: enterdEmail,
    hasError: emailHasError,
    isValid: IsEmailValid,
    valueChangedHandler: handleEmailChange,
    inputBlurHandler: handleBlurEmailChange,
    reset: handlerEmailReset,
  } = useInput((value) => value.includes("@"));

  const handleSubmit = async (event) => {
    event.preventDefault();
    console.log("Handle Submit Called");
    console.log("Email value : " + event.target.email.value);
    console.log("Name value : " + event.target.name.value);

    handlerReset();
    handlerEmailReset();
  };

  return (
    <body>
      <h3>Custom Hook</h3>
      <form onSubmit={handleSubmit}>
        <table border="1px solid">
          <tr>
            <td>Please enter the Email</td>
            <td>
              <input
                id="email"
                type="email"
                onChange={handleEmailChange}
                onBlur={handleBlurEmailChange}
                value={enterdEmail}
              ></input>
            </td>
          </tr>
          <tr>
            <td colSpan="2">
              {!IsEmailValid && (
                <p className="invalidInput">Please enter a valid email</p>
              )}
            </td>
          </tr>
          <tr>
            <td>Please enter the Name</td>
            <td>
              <input
                id="name"
                type="text"
                onChange={handleValueChange}
                onBlur={handleBlueChange}
                value={enterdName}
              ></input>
            </td>
          </tr>
          <tr>
            <td colSpan="2">
              {IsNameValid && (
                <p className="invalidInput">Name can't be empty</p>
              )}
            </td>
          </tr>
          <tr>
            <td colSpan="3">
              <button className="submitBtn" >
                Submit
              </button>
            </td>
          </tr>
        </table>
      </form>
    </body>
  );
}

Output

Execute the code using.

npm start and enter a valid email address and name

How To Create Custom Hook

Click on submit and consider the console output. And it will automatically blank the value using the reset property method.

Handle Submit Called
BaseForm.js:25 Email value : [email protected]
BaseForm.js:26 Name value : Piyush

​​​​​​​Now enter the invalid email and keep the name blank.

How To Create Custom Hook

It will show the error.

​​​​​​​So, we have seen how much we have minimized the code and reduced code repetition. This way, we can write custom hooks that prevent code repetition and provide code minimization. We can write custom hooks for many implementations like generating sliders, generalizing functions, or any task implementation.