React Custom Hooks: Beginner’s Guide with Examples

In React, Hooks like useState, useEffect, useRef, and useMemo let us add state and side effects to functional components.

Sometimes, multiple components share the same logic — for example:

  • Fetching data from an API

  • Managing form inputs

  • Handling window resizing

Instead of duplicating code, we can create a Custom Hook.

 A Custom Hook is a JavaScript function whose name starts with use and can call other hooks.

Why Use Custom Hooks?

  • Reusability: Share logic across multiple components

  • Clean code: Reduce duplication and make components simpler

  • Separation of concerns: Isolate logic from UI

Creating a Custom Hook

Example 1: useCounter Hook

import { useState } from "react";

// Custom Hook
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(initialValue);

  return { count, increment, decrement, reset };
}

export default useCounter;

Using the Custom Hook

import React from "react";
import useCounter from "./useCounter";

function CounterComponent() {
  const { count, increment, decrement, reset } = useCounter(5);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increase</button>
      <button onClick={decrement}>Decrease</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

useCounter logic is reusable in any component.

Example 2: useFetch Hook

import { useState, useEffect } from "react";

// Custom Hook to fetch API data
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return { data, loading };
}

export default useFetch;

Using useFetch

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

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

  if (loading) return <p>Loading...</p>;

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

The API fetching logic is separated and reusable.

Rules for Custom Hooks

  • Function name must start with use

  • Can call other hooks inside the custom hook

  • Cannot be called conditionally; always follow hook rules

  • Can return state, functions, objects, or arrays

Best Practices

PracticeExplanation
ReusabilityCreate hooks for logic used across multiple components.
Separation of concernsKeep UI components simple; logic goes in the custom hook.
Return consistent structureReturn an object or array for predictability.
Use descriptive namesuseCounter, useFetch, useForm — names should describe the hook purpose.
Combine hooksCustom hooks can use useState, useEffect, useRef, etc., internally.

FAQ

Q1: Can custom hooks have state?
 Yes, you can use useState or useReducer inside custom hooks.

Q2: Can I call multiple hooks inside a custom hook?
 Yes, a custom hook can combine multiple built-in hooks for complex logic.

Q3: Are custom hooks reusable across projects?
 Absolutely! You can extract them to a shared library or npm package.