Lesson 36 of 60 – Custom Hooks
60%

Custom Hooks

A Custom Hook is a reusable JavaScript function that uses one or more React Hooks. Custom Hooks allow developers to share stateful logic between multiple components without duplicating the same code.

Note: A Custom Hook usually starts with the word use, such as useCounter, useFetch, or useForm.

1. What is a Custom Hook?

A Custom Hook is a JavaScript function created by the developer to reuse React Hook logic across components.

function useCounter() {

    // Hook logic

}

Custom Hooks help keep components smaller and make reusable logic easier to maintain.

2. Why Use Custom Hooks?

Suppose several components need the same state and event logic. Instead of copying the same code into every component, the logic can be placed inside a Custom Hook.

  • Reduces duplicate code
  • Improves code organization
  • Encourages reusable logic
  • Keeps components cleaner
  • Makes complex logic easier to manage

3. Naming a Custom Hook

Custom Hook names should start with use.

useCounter()
useForm()
useFetch()
useWindowSize()
useLocalStorage()

Starting the name with use follows React's Hook naming convention.

4. Basic Custom Hook Syntax

function useSomething() {

    // React Hook logic

    return value;
}

A Custom Hook can use React Hooks and return values that a component needs.

5. Custom Hook with useState

A Custom Hook can use useState.

import { useState } from "react";

function useCounter() {

    const [count, setCount] =
        useState(0);

    return {
        count,
        setCount
    };
}

The component can use the returned values.

6. Using a Custom Hook

function Counter() {

    const {
        count,
        setCount
    } = useCounter();

    return (
        <button
            onClick={() =>
                setCount(count + 1)
            }
        >
            {count}
        </button>
    );
}

7. Simple useCounter Hook

import { useState } from "react";

function useCounter(initialValue = 0) {

    const [count, setCount] =
        useState(initialValue);

    function increment() {
        setCount(
            previous =>
                previous + 1
        );
    }

    function decrement() {
        setCount(
            previous =>
                previous - 1
        );
    }

    function reset() {
        setCount(initialValue);
    }

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

export default useCounter;

8. Using useCounter

import useCounter
from "./useCounter";

function App() {

    const {
        count,
        increment,
        decrement,
        reset
    } = useCounter(10);

    return (
        <div>

            <h2>
                Count: {count}
            </h2>

            <button onClick={increment}>
                +
            </button>

            <button onClick={decrement}>
                -
            </button>

            <button onClick={reset}>
                Reset
            </button>

        </div>
    );
}

9. Custom Hooks Can Use Other Hooks

A Custom Hook can use React Hooks such as:

  • useState
  • useEffect
  • useRef
  • useContext
  • useMemo
  • useCallback

This makes Custom Hooks useful for packaging reusable stateful logic.

10. Custom Hook with useEffect

A Custom Hook can combine useState and useEffect.

import {
    useEffect,
    useState
} from "react";

function useDocumentTitle(title) {

    useEffect(() => {

        document.title = title;

    }, [title]);

}

export default useDocumentTitle;

11. Using useDocumentTitle

function Profile() {

    useDocumentTitle(
        "Student Profile"
    );

    return (
        <h1>
            Student Profile
        </h1>
    );
}

The Custom Hook hides the document-title logic from the component.

12. Custom Hook with useEffect and useState

function useOnlineStatus() {

    const [online, setOnline] =
        useState(
            navigator.onLine
        );

    useEffect(() => {

        function handleOnline() {
            setOnline(true);
        }

        function handleOffline() {
            setOnline(false);
        }

        window.addEventListener(
            "online",
            handleOnline
        );

        window.addEventListener(
            "offline",
            handleOffline
        );

        return () => {

            window.removeEventListener(
                "online",
                handleOnline
            );

            window.removeEventListener(
                "offline",
                handleOffline
            );

        };

    }, []);

    return online;
}

13. Using useOnlineStatus

function Status() {

    const online =
        useOnlineStatus();

    return (
        <h2>
            {online
                ? "Online"
                : "Offline"}
        </h2>
    );
}

The component does not need to know how the online status is detected.

14. Custom Hook for Forms

Form logic is a common use case for Custom Hooks.

function useInput(initialValue = "") {

    const [value, setValue] =
        useState(initialValue);

    function handleChange(event) {
        setValue(event.target.value);
    }

    return {
        value,
        onChange: handleChange
    };
}

15. Using a Form Custom Hook

function Login() {

    const username =
        useInput("");

    const password =
        useInput("");

    return (
        <form>

            <input
                type="text"
                placeholder="Username"
                {...username}
            />

            <input
                type="password"
                placeholder="Password"
                {...password}
            />

        </form>
    );
}

The spread operator passes the returned value and onChange properties to the inputs.

16. Custom Hook for API Data

A Custom Hook can package common API-loading logic.

function useFetch(url) {

    const [data, setData] =
        useState(null);

    const [loading, setLoading] =
        useState(true);

    const [error, setError] =
        useState(null);

    // Fetch logic here

    return {
        data,
        loading,
        error
    };
}

17. useFetch Example

function useFetch(url) {

    const [data, setData] =
        useState(null);

    const [loading, setLoading] =
        useState(true);

    const [error, setError] =
        useState(null);

    useEffect(() => {

        fetch(url)
            .then(response => {

                if (!response.ok) {
                    throw new Error(
                        "Request failed"
                    );
                }

                return response.json();
            })
            .then(result => {

                setData(result);
                setLoading(false);

            })
            .catch(error => {

                setError(error);
                setLoading(false);

            });

    }, [url]);

    return {
        data,
        loading,
        error
    };
}

18. Using useFetch

function Students() {

    const {
        data,
        loading,
        error
    } = useFetch(
        "/api/students"
    );

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

    if (error) {
        return <p>
            Error loading data
        </p>;
    }

    return (
        <div>
            {data.map(student => (
                <p key={student.id}>
                    {student.name}
                </p>
            ))}
        </div>
    );
}

19. Custom Hooks and Parameters

Custom Hooks can accept parameters just like normal JavaScript functions.

function useCounter(initialValue) {

    const [count, setCount] =
        useState(initialValue);

    return {
        count,
        setCount
    };
}

A component can provide a different initial value.

const counter =
    useCounter(100);

20. Custom Hooks Can Return Objects

A Custom Hook can return an object containing multiple values and functions.

return {
    value,
    loading,
    error,
    reset,
    submit
};

The component can destructure only the values it needs.

21. Custom Hooks Can Return Arrays

A Custom Hook can also return an array.

return [
    value,
    setValue
];

The component can use array destructuring.

const [
    value,
    setValue
] = useCustomHook();

22. Rules of Custom Hooks

Custom Hooks follow the same Rules of Hooks as built-in Hooks.

  • Call Hooks only at the top level.
  • Do not call Hooks inside loops.
  • Do not call Hooks inside conditions.
  • Call Hooks only from React components or Custom Hooks.
  • Custom Hook names should start with use.

23. Custom Hooks Do Not Share State Automatically

If two components call the same Custom Hook, they normally receive their own independent state.

function CounterOne() {

    const counter =
        useCounter(0);

    return (
        <h2>
            {counter.count}
        </h2>
    );
}

function CounterTwo() {

    const counter =
        useCounter(0);

    return (
        <h2>
            {counter.count}
        </h2>
    );
}

The state belongs to each component instance.

24. Custom Hook vs Normal Function

Normal Function Custom Hook
General JavaScript logic. Reusable React stateful logic.
Does not normally use React Hooks. Can use React Hooks.
Can have any valid function name. Usually starts with use.
Used for general calculations or utilities. Used for reusable component logic.

25. Organizing Custom Hooks

A common project structure is to keep Custom Hooks in a dedicated hooks folder.

src/
│
├── components/
│
├── pages/
│
├── hooks/
│   ├── useCounter.js
│   ├── useFetch.js
│   └── useForm.js
│
├── App.jsx
└── main.jsx

This makes reusable Hook logic easy to find.

26. Custom Hook with Local Storage

A Custom Hook can encapsulate local storage logic.

function useLocalStorage(
    key,
    initialValue
) {

    const [value, setValue] =
        useState(() => {

            const saved =
                localStorage.getItem(key);

            return saved
                ? JSON.parse(saved)
                : initialValue;

        });

    useEffect(() => {

        localStorage.setItem(
            key,
            JSON.stringify(value)
        );

    }, [key, value]);

    return [
        value,
        setValue
    ];
}

27. Using useLocalStorage

function App() {

    const [
        name,
        setName
    ] = useLocalStorage(
        "name",
        ""
    );

    return (
        <div>

            <input
                value={name}
                onChange={e =>
                    setName(
                        e.target.value
                    )
                }
            />

            <p>
                Name: {name}
            </p>

        </div>
    );
}

The component can use local storage without directly handling the storage implementation.

28. Common Custom Hook Mistakes

  • Not starting a Custom Hook name with use.
  • Calling Hooks inside conditions or loops.
  • Calling Hooks from regular utility functions incorrectly.
  • Putting unrelated logic into one large Custom Hook.
  • Forgetting dependencies in useEffect.
  • Returning unclear or inconsistent values.
  • Duplicating logic instead of extracting reusable behavior.

29. Custom Hook Workflow

The general workflow for creating a Custom Hook is:

  1. Identify logic that is repeated across components.
  2. Create a function whose name starts with use.
  3. Move the reusable Hook logic into the function.
  4. Accept required values as parameters.
  5. Return the values or functions needed by the component.
  6. Import the Custom Hook where it is required.
  7. Call the Custom Hook at the top level of the component.
Repeated Logic
      ↓
Create Custom Hook
      ↓
useState / useEffect / Other Hooks
      ↓
Return Values
      ↓
Use in Components

30. Custom Hooks Best Practices

  • Give Custom Hooks meaningful names beginning with use.
  • Keep each Hook focused on a specific reusable behavior.
  • Follow all Rules of Hooks.
  • Use parameters to make Hooks reusable.
  • Return a clear and useful API.
  • Keep related logic together.
  • Place reusable Hooks in a dedicated hooks folder when appropriate.
  • Do not create a Custom Hook simply for a very small piece of unrelated logic.
  • Keep dependency arrays accurate.
  • Reuse logic without duplicating component implementation.

📌 Key Points

  • A Custom Hook is a reusable JavaScript function containing React Hook logic.
  • Custom Hook names should start with use.
  • Custom Hooks can use built-in Hooks such as useState and useEffect.
  • They help reduce duplicated stateful logic.
  • Custom Hooks can accept parameters.
  • They can return objects, arrays, values, or functions.
  • Each component normally gets its own state when calling a Custom Hook.
  • Custom Hooks follow the Rules of Hooks.
  • Common examples include useCounter, useFetch, and useForm.
  • Custom Hooks make reusable React logic easier to organize and maintain.

🧠 Quick Quiz

Question: What is the main purpose of a Custom Hook in React?