How to Implement Custom Hooks in React

This article provides a practical guide on how to create and implement custom hooks in React. You will learn the core concepts behind custom hooks, understand when to implement them to share stateful logic between components, and walk through a step-by-step example of building and consuming your own custom hook.

What is a Custom Hook?

A custom hook is a JavaScript function whose name starts with “use” and that can call other hooks. In React, custom hooks allow you to extract component logic into reusable functions. Instead of copying and pasting the same useState or useEffect logic across multiple components, you bundle that logic into a single custom hook.

Rules of Custom Hooks

When implementing custom hooks, you must follow two strict React rules: 1. Name must start with “use”: React uses this naming convention (e.g., useAuth, useFetch) to automatically check for violations of the rules of hooks. 2. Call hooks at the top level: Do not call custom hooks inside loops, conditions, or nested functions.

Step-by-Step Implementation

To implement a custom hook, follow these three steps: 1. Identify Reusable Logic: Find stateful logic (using useState, useEffect, etc.) that is repeated in your components. 2. Extract to a Separate Function: Move this logic into a new function that starts with use. 3. Return State or Functions: Return the values, states, or updater functions that your UI components need.


Practical Example: Creating a useFetch Hook

Below is a step-by-step implementation of a custom hook that fetches data from an API and manages loading and error states.

Step 1: Define the Custom Hook

Create a new file named useFetch.js. This hook will manage data, loading, and error states, and execute the API call inside a useEffect block.

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

export default useFetch;

Step 2: Use the Custom Hook in a Component

Now, you can import and use the useFetch hook in any component. This keeps your component UI clean and separated from the data-fetching logic.

import React from 'react';
import useFetch from './useFetch';

function UserList() {
  const { data: users, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');

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

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default UserList;

Key Takeaways