How to Create Custom Hooks in React for Reusability

How to Create Custom Hooks in React for Reusability

React Custom Hooks are an invaluable tool that allow developers to create reusable, maintainable and more scalable applications by allowing for the extraction of logic into reusable functions. You can then import this functionality into any of your components and thus save yourself a headache down the line when you need to make updates to this logic.

In this article, I'll explore how to create custom hooks in React, understand when and why to use them, and walk through some practical examples. By the end, you'll have a solid grasp of how to leverage custom hooks to streamline your React development. 

What Are Custom Hooks?

Before diving into the "how," it's important to understand the "what". React custom hooks are functions that allow you to encapsulate code functionality and reuse logic that would otherwise be duplicated across multiple components. They start with the word use and can utilize other hooks (such as useStateuseEffect, etc.) inside of them as well.

Essentially, custom hooks are a way to reuse stateful logic without the need for higher-order components (HOCs) or render props. They are a perfect solution when you want to share logic between components while keeping the code DRY (Don't Repeat Yourself).

If you are using either useState or useEffect already, then you know how they work for the most part.

Why Use Custom Hooks?

And now that we have the "what", here is the "why". The main advantage of custom hooks is reusability essentially. By moving shared logic into a hook, you can avoid code duplication, making your application easier to manage and maintain as it grows in the future.

More importantly, you avoid possible future errors that can happen with code duplication, such as updating the code in one location, but forgetting to update it in another, thus leading to logic errors.

Additionally, custom hooks can help with:

Abstraction: Encapsulate complex logic in a single place where it doesn't interfere with other code outside of its scope.

Separation of Concerns: Keep components focused on rendering and delegate logic to hooks.

Cleaner Code: Reduce clutter in your components by moving logic to hooks.

When to Create a Custom Hook

Creating a custom hook makes sense when you find yourself needing to reuse the same logic in multiple components. If you notice a pattern emerging where the same stateful logic is needed in different parts of your application, that’s a signal that it might be time to create a custom hook.

For me personally, if a function of code is needed in more than one place then it is an ideal candidate.

For example:

Fetching data: If you have multiple components that need to fetch data from an API.

Form handling: If you have several forms that share similar logic.

Managing local storage: If multiple components need to read or write to local storage.

How to Create a React Custom Hook

Now, let's get into the nuts and bolts of creating a custom hook.

Step 1: Identify the Logic to Extract

Start by identifying the logic that you want to extract and reuse. Let's say we have a component that fetches user data and we need to use this logic in multiple components around our site.

import { useState, useEffect } from 'react';

function UserComponent() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/user')
      .then(response => response.json())
      .then(data => setUser(data));
  }, []);

  return (
    <div>
      {user ? <h1>{user.name}</h1> : <p>Loading...</p>}
    </div>
  );
}

For example, you might have the user's profile picture and name in the top menu icon of a page, and also in their dashboard page. Same data, same fetch, yet often times you will see this type of code repeated.

Step 2: Create the Hook

Move the logic into a new function that starts with the word use. The function will return the data and any functions you need to manipulate the data as well.

import { useState, useEffect } from 'react';

function useUserData() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/user')
      .then(response => response.json())
      .then(data => setUser(data));
  }, []);

  return user;
}

Hooks are typically created in their own files and many developers will keep them in their own dedicated 'hooks' folder somewhere in the 'src' directory.

src/
+-- components/
+-- hooks/
¦   +-- useUserData.js
¦   +-- useForm.js
+-- App.js
+-- index.js

In the example above, notice that the hook just handles fetching the user data and then returning it back to the caller.

Step 3: Use the Hook in another Component

Now that you've extracted the logic into a custom hook, you can use it in any component that needs this functionality.

import React from 'react';
import useUserData from '../hooks/useUserData'; // Importing the custom hook

function UserComponent() {
  const user = useUserData(); // Using the custom hook

  return <h1>{user.name}</h1>;
}

export default UserComponent;

You can now reuse useUserData in multiple components without duplicating code.

Advanced Custom Hook Examples

Custom hooks aren't limited to simple use cases, like the example above. You typically want to create hooks for more complex scenarios that do best as standalone elements.

Here are a few examples of those situations:

Example 1: Fetching Data with Error Handling

We can further improve the useUserData hook to include loading and error states.

function useUserData() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch('https://api.example.com/user')
      .then(response => response.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, []);

  return { user, loading, error };
}

Now, in your component, you can easily access the user data along with the loading and error states.

function UserComponent() {
  const { user, loading, error } = useUserData();

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return <h1>{user.name}</h1>;
}

Example 2: A Custom Hook for Form Handling

Form handling is another common scenario where custom hooks make sense. Let's create a hook to manage form state.

import { useState } from 'react';

function useForm(initialState) {
  const [values, setValues] = useState(initialState);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues({
      ...values,
      [name]: value,
    });
  };

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

  return {
    values,
    handleChange,
    resetForm,
  };
}

Using this hook in a form component, looks something like the following:

function SignupForm() {
  const { values, handleChange, resetForm } = useForm({
    username: '',
    email: '',
    password: '',
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    resetForm();
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={values.username}
        onChange={handleChange}
        placeholder="Username"
      />
      <input
        name="email"
        value={values.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        name="password"
        value={values.password}
        onChange={handleChange}
        placeholder="Password"
        type="password"
      />
      <button type="submit">Sign Up</button>
    </form>
  );
}

Best Practices for Creating Custom Hooks

When creating custom hooks, it's essential to follow best practices to ensure they are maintainable and useful.

Naming convention: Always start your custom hook's name with use. This is not just a convention but also helps React identify whether certain rules of hooks apply.

Return values: Return the values or functions that the consuming component will need. Be thoughtful about what you expose to the outside world.

Avoid side effects: Your hook should be primarily focused on logic and not on rendering side effects. If side effects are necessary, manage them cleanly within useEffect.

Reusability: Keep your hooks focused and generic enough to be reusable in different contexts. Avoid making them too specific to a single component's needs.

Testing: Ensure your custom hooks are well-tested. Since they encapsulate logic, they can be easily unit-tested.

Conclusion

Custom hooks are a powerful tool in the React developer’s arsenal. They allow you to extract and reuse component logic efficiently, making your codebase cleaner and more maintainable. By following the steps outlined in this article, you can start creating your custom hooks, making your development process more streamlined and efficient.

Walter G. author of blog post
Walter Guevara is a Computer Scientist, software engineer, startup founder and previous mentor for a coding bootcamp. He has been creating software for the past 20 years.

Get the latest programming news directly in your inbox!

Have a question on this article?

You can leave me a question on this particular article (or any other really).

Ask a question

Community Comments

I
Ibrahim
8/27/2024 11:31:48 PM
I've been using react for 2 years, and i stayed away from hooks because they made no sense. But damn, i finally get it. Thanks Walter.

Add a comment