← Back to Chapters

Custom Hooks in React

⚛️ Custom Hooks in React

React Hooks • Reusable Logic

? Quick Overview

A Custom Hook is a reusable function in React that starts with the word use and allows you to extract and share logic between multiple components.

They make your code cleaner, reduce duplication, and improve maintainability — especially when working with complex logic like fetching data, form handling, or listening to browser events.

? Key Concepts

? Why Create Custom Hooks?

  • Reuse logic across components without repeating code.
  • Abstract complex functionality into simple, reusable functions.
  • Separate UI (components) from behavior (hooks).
  • Improve readability and testability.

? Rules for Custom Hooks

  • ✅ Must start with use (e.g., useFetch, useForm).
  • ✅ Can use other hooks like useState, useEffect, etc.
  • ❌ Cannot be called conditionally or inside loops (same as normal hooks).
  • ✅ Should encapsulate logic, not JSX markup.

? Syntax & Theory

A custom hook is just a JavaScript function that:

  • Is named with the use prefix (so React can track hook calls).
  • Calls one or more built-in hooks like useState, useEffect, etc.
  • Returns data and/or functions that components can use.

Conceptually, you are moving logic out of components into a shared function so that:

  • The UI layer stays focused on rendering.
  • The hook layer handles state, side effects, and business rules.

? Code Examples

? Example 1: useCounter Hook

? View Code Example – useCounter.js
// useCounter.js - reusable counter logic hook
import { useState } from "react";

export default function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

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

  return { count, increment, decrement, reset };
}
? View Code Example – App.js using useCounter
// App.js - UI component that consumes useCounter
import React from "react";
import useCounter from "./useCounter";

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

  return (
    <div className="text-center">
      <h4>Count: {count}</h4>
      <button className="btn btn-primary me-2" onClick={increment}>+</button>
      <button className="btn btn-danger me-2" onClick={decrement}>-</button>
      <button className="btn btn-secondary" onClick={reset}>Reset</button>
    </div>
  );
}

export default App;

? What This Does

The useCounter hook encapsulates all counter-related logic: state, increment/decrement handlers, and reset. The App component simply renders buttons and displays the current count.

When you click +, the count increases; when you click -, it decreases; clicking Reset returns the count to the initial value 10. The same hook can be reused in any component that needs a counter.

? Example 2: useFetch Hook

? View Code Example – useFetch.js
// useFetch.js - reusable data fetching hook
import { useState, useEffect } from "react";

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

  useEffect(() => {
    setLoading(true);
    setError(null);

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

  return { data, loading, error };
}
? View Code Example – UserList.js using useFetch
// UserList.js - component that consumes useFetch
import React from "react";
import useFetch from "./useFetch";

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

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error fetching data!</p>;

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

export default UserList;

? What This Does

The useFetch hook centralizes the logic for fetching data, tracking loading state, and handling errors. The UserList component only cares about how to render the result.

While data is loading, the user sees “Loading...”. If the request fails, an error message is shown. On success, a list of user names fetched from the API is rendered.

⚙️ Example 3: useWindowSize Hook

? View Code Example – useWindowSize.js
// useWindowSize.js - track browser window size
import { useState, useEffect } from "react";

export default function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({ width: window.innerWidth, height: window.innerHeight });
    };

    window.addEventListener("resize", handleResize);

    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return size;
}
? View Code Example – DisplaySize.js using useWindowSize
// DisplaySize.js - shows current viewport dimensions
import React from "react";
import useWindowSize from "./useWindowSize";

function DisplaySize() {
  const { width, height } = useWindowSize();

  return (
    <h5 className="text-center">
      Width: {width}px | Height: {height}px
    </h5>
  );
}

export default DisplaySize;

? What This Does

The useWindowSize hook listens to the browser resize event and keeps the current width and height in state. The DisplaySize component simply shows those values.

As you resize the browser window, the numbers update automatically, making this hook useful for responsive layouts and adaptive components.

? Benefits of Custom Hooks

  • ? Reusable logic across components.
  • ? Cleaner, more maintainable code.
  • ⚡ Improved readability by separating concerns.
  • ? Easier testing by isolating business logic from UI.

? Tips & Best Practices

  • Name your hooks descriptively (e.g., useAuth, useForm, useToggle).
  • Keep them focused on logic – avoid returning JSX from hooks.
  • Always start hook names with use so React can enforce the rules.
  • Call hooks only at the top level of your custom hook (no loops, conditions, or nested functions).
  • Test hook logic independently using Jest or React Testing Library.
  • Share reusable hooks via npm packages or your team’s internal libraries.

? Try It Yourself

  1. Create a useToggle hook that toggles between true/false.
  2. Write a useLocalStorage hook that syncs state with browser localStorage.
  3. Build a useOnlineStatus hook that detects when the user goes offline or online.
  4. Refactor one of your existing components to use a custom hook so that UI and logic are clearly separated.

Goal: Learn to design and implement reusable logic blocks in React using custom hooks — making your apps modular, efficient, and easy to maintain.