Create a Custom React Hook

Queen Nnakwue
author
Queen Nnakwue
Yellow rings on a yellow background

Sometimes when building React applications, we want to share logic between JavaScript functions. To do this as a developer, we extract the particular logic and put it in a function, right? These functions in React are what we call Custom Hooks.

Custom hooks are functions that we create to make logic accessible to other components or functions in an application. As with other hooks in React, a custom hook must:

  • Be called at the very top level of your React function to ensure that hooks are called in the same order each time a component renders.
  • Start with the use keyword
  • Never be called inside a nested function, a loop, or condition
  • Only be called from functional components in React

Prerequisites

A basic knowledge of React and React hooks is required to get the full value out of this article.

Building Your First Custom Hooks

To demonstrate how to create a custom hook, I will divide this lesson into two parts. In the first half, we will write code without any custom hooks, and in the second part, we will move some logic into a custom hook. Are you excited? Let's get started.

For this example, we will implement a simple form where a user can submit their first and last name. To begin, let's create a new React app by running npx create-react-app custom-hooks. After creating your react app, create a components folder inside your src folder. Inside your components folder, create a UserForm.js file. Your folder structure should look like this:

Now, inside your UserForm.js file create a functional component. We need two state variables for our firstName and lastName(remember we want to create a submit form where users can submit their first and last names), so we are going to import the useState hook in react at the top of our file and create the two variables.

Next, let's add the jsx. Inside your return statement, add a form tag that will contain some input elements for our first and last name. Now your UserForm.js file should look like this:

import React from 'react';
import { useState } from 'react';
export default function UserForm() {
const [firstName, setfirstName] = useState('');
const [lastName, setlastName] = useState('');
return(
<>
<form>
<div>
<label>First Name</label>
<input type='text' />
</div>
<div>
<label>Last Name</label>
<input type='text' />
</div>
<button>Submit</button>
</form>
</>
)
}

The next step is to convert these input elements into a controlled component like so:

import React from 'react';
import { useState } from 'react';
export default function UserForm() {
const [firstName, setfirstName] = useState('');
const [lastName, setlastName] = useState('');
return(
<>
<form>
<div>
<label>First Name</label>
<input
value={firstName}
onChange={e =>setfirstName(e.target.value)}
type='text' />
</div>
<div>
<label>Last Name</label>
<input
value={lastName}
onChange={e =>setlastName(e.target.value)}
type='text'
/>
</div>
<button>Submit</button>
</form>
</>
)
}

Noticed from the above, we added value and onChange attributes (where e in onChange stands for “event”), to our input elements to make them interactive. Next, we are going to add a submitHandler to our form tag and create the submitHandler function. Inside our submitHandler function, let’s add the e.preventdefault() method to stop the code from refreshing and an alert method which we will use to display our input names like so:

const submitHandler = e => {
e.preventDefault();
alert(`Hello ${firstName} ${lastName}`)
}

We are almost done with the first part of our code. Your UserForm function should look like this:

const [firstName, setfirstName] = useState('');
const [lastName, setlastName] = useState('');
const submitHandler = e => {
e.preventDefault();
alert(`Hello ${firstName} ${lastName}`);
}
return(
<>
<form onSubmit={submitHandler}>
<div>
<label>First Name</label>
<input
value={firstName}
onChange={e =>setfirstName(e.target.value)}
type='text' />
</div>
<div>
<label>Last Name</label>
<input
value={lastName}
onChange={e =>setlastName(e.target.value)}
type='text' />
</div>
<button>Submit</button>
</form>
</>
)

One last thing before we finish the first part of our code. Let's go to our App.js file, remove the boilerplate code, import our UserForm component, and return it like so:

import React from 'react';
import UserForm from './components/UserForm';
function App() {
return (
<div>
<UserForm />
</div>
);
}
export default App;

Next, go to your terminal, inside your project directory, and run npm start. You should get redirected to your browser. Add a first and last name inside the input fields and submit. Notice the alert method returned your inputs prefixed by the "Hello" word we added?

Pretty straightforward, yeah? Now, let's create our custom hook. For this example, we want our custom hook to encapsulate controlled component behavior for our input element. In other words, we want to be able to bind the value and onChange attributes together since they are repeated severally in our UserForm.js file. Let's see how:

  • Create a new folder called hooks inside of your src folder
  • Inside your hooks folder, create a file called useInput.js.

In our useInput custom hook, we want to create a logic that can track our input fields' value. To implement this logic, we would need to import useState from React and set our useState to a variable with a parameter of initialValue like so:

import { useState } from 'react';
export default function useInput(initialValue){
const [value, setValue] = useState(initialValue);
}

Now that we have the value and a function to update the value- setValue, let's add our logic.

First, let's create an object called reset that allows us to set the value back to the initial value (initialValue) and an object called bind with two properties. The first property is value, and the second property is onChange. The onChange property is going to be a function that receives the event as a parameter and sets the value to e.target.value

Finally, we will return three things; the value, bind, and reset like so:

import { useState } from 'react';
export default function useInput(initialValue){
const [value, setValue] = useState(initialValue);
const reset = () => {
setValue(initialValue);
}
const bind = {
value,
onChange: e => {
e.target.value
}
};
return [value, bind, reset];
}

Next, let's see how to incorporate this logic in our component. In your useForm.js file, import the useInput file on the top of your page. After that, just underneath your UserForm function, write the following lines of code to replace the useState call for the firstName.

const [firstName, bindFirstName, resetFirstName] = useInput('');
const [lastName, bindLastName, resetLastName] = useInput('')

Remember that a call to useInput in our useInput file returns three values (firstName, bindFirstName, resetFirstName). Here in our UserForm file, we destructured all three and deleted our useState call for both firstName and lastName.

Noticed that firstName and lastName has already been used in the submitHandler function. Now, to use the bindFirstName, bindLastName, resetFirstName and resetLastName, we would need to replace the value and onChange attributes for firstName while bindLastName would need to replace the value and onChange attributes for lastName in our form. This is possible because the code is in fact one and the same. Something like this:

<form onSubmit={submitHandler}>
<div>
<label>First Name</label>
<input
{...bindFirstName}
type='text' />
</div>
<div>
<label>Last Name</label>
<input
{...bindLastName}
type='text' />
</div>
<button>Submit</button>
</form>

One last thing before we round up with this. For the reset functions, we can make use of them in the submitHandler like so:

const submitHandler = e => {
e.preventDefault();
alert(`Hello ${firstName} ${lastName}`);
resetFirstName();
resetLastName();
}

In the end, your userForm file should look like this:

import React from 'react';
import { useState } from 'react';
import useInput from '../hooks/useInput';
export default function UserForm() {
const [firstName, bindFirstName, resetFirstName] = useInput('');
const [lastName, bindLastName, resetLastName] = useInput('')
const submitHandler = e => {
e.preventDefault();
alert(`Hello ${firstName} ${lastName}`);
resetFirstName();
resetLastName();
}
return(
<>
<form onSubmit={submitHandler}>
<div>
<label>First Name</label>
<input
{...bindFirstName}
type='text' />
</div>
<div>
<label>Last Name</label>
<input
{...bindLastName}
type='text' />
</div>
<button>Submit</button>
</form>
</>
)
}

That's pretty much it. You can save the file and take a look at your browser. Noticed after you try inputting, the form refreshes itself (courtesy of the reset button), showing us that our useInput custom hook works as expected.

You can find the complete code here in my GitHub repository- https://github.com/QNNAKWUE/custom-hooks.