Welcome back to the React Mastery Series!

In the previous article, we explored useReducer and learned how it helps manage complex state transitions in a predictable way.

As React applications grow, another challenge emerges:

How do we avoid repeating the same logic across multiple components?

Imagine implementing:

  • API calls
  • Authentication
  • Form validation
  • Window resize detection
  • Debouncing search input
  • Online/offline status monitoring

If every component contains the same logic, the application quickly becomes difficult to maintain.

React solves this elegantly using:

Custom Hooks

Custom Hooks allow you to extract reusable stateful logic into reusable functions.


What is a Custom Hook?

A Custom Hook is simply a JavaScript function that:

  • Starts with the word use
  • Can use other React Hooks
  • Encapsulates reusable logic
  • Can be shared across multiple components

Example:

function useCounter() {

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

  const increment = () => setCount(c => c + 1);

  return {
    count,
    increment
  };
}

Enter fullscreen mode Exit fullscreen mode

Now any component can use it like a built-in Hook.

const { count, increment } = useCounter();

Enter fullscreen mode Exit fullscreen mode


Why Do We Need Custom Hooks?

Consider two components.

User Dashboard

useEffect(() => {
  fetchUsers();
}, []);

Enter fullscreen mode Exit fullscreen mode

Admin Dashboard

useEffect(() => {
  fetchUsers();
}, []);

Enter fullscreen mode Exit fullscreen mode

Both components contain identical logic.

This leads to:

  • Duplicate code
  • Difficult maintenance
  • Higher chance of bugs

Instead, extract the logic into a Custom Hook.


Creating Your First Custom Hook

Example:

import { useState } from "react";

function useCounter() {

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

  const increment = () =>
    setCount(c => c + 1);

  const decrement = () =>
    setCount(c => c - 1);

  return {
    count,
    increment,
    decrement
  };

}

Enter fullscreen mode Exit fullscreen mode

Using it:

function Counter() {

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

  return (
    <>
      <h2>{count}</h2>

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

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

}

Enter fullscreen mode Exit fullscreen mode

The logic is reusable and isolated.


Real-World Example: Fetching Data

Many components fetch data from APIs.

Instead of repeating this logic, create a reusable Hook.

function useUsers() {

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

  useEffect(() => {

    fetch("/api/users")
      .then(response => response.json())
      .then(data => {
        setUsers(data);
        setLoading(false);
      });

  }, []);

  return {
    users,
    loading
  };

}

Enter fullscreen mode Exit fullscreen mode

Now multiple components can simply do:

const {users, loading} = useUsers();

Enter fullscreen mode Exit fullscreen mode

No duplicated fetching logic.


Example: Window Width Hook

Imagine several pages need the browser width.

Instead of adding event listeners everywhere:

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener("resize", handleResize);

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

  }, []);

  return width;
}

Enter fullscreen mode Exit fullscreen mode

Usage:

const width = useWindowWidth();

Enter fullscreen mode Exit fullscreen mode

Every component now shares the same reusable logic.


Example: Online Status Hook

Modern applications often need to know if the user is online.

function useOnlineStatus() {

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

  useEffect(() => {
    const goOnline = () => setOnline(true);
    const goOffline = () => setOnline(false);
    window.addEventListener("online",goOnline);
    window.addEventListener("offline",goOffline);

    return () => {
      window.removeEventListener("online",goOnline);
      window.removeEventListener("offline",goOffline);
    };
  }, []);
  return online;
}

Enter fullscreen mode Exit fullscreen mode

Usage:

const isOnline = useOnlineStatus();

Enter fullscreen mode Exit fullscreen mode

Perfect for:

  • Banking apps
  • Chat applications
  • Video conferencing
  • Trading platforms

Custom Hooks Can Use Other Hooks

A common misconception is that Custom Hooks are different from React Hooks.

In reality, they are built using existing Hooks.

Example:

Custom Hook
     |
     ├── useState
     ├── useEffect
     ├── useContext
     ├── useReducer
     └── useRef

Enter fullscreen mode Exit fullscreen mode

You can combine multiple Hooks inside one Custom Hook.


Sharing Logic, Not State

A very important concept:

Each component gets its own independent state.

Example:

const counter1 = useCounter();

const counter2 = useCounter();

Enter fullscreen mode Exit fullscreen mode

Result:

Counter 1 → 5

Counter 2 → 12

Enter fullscreen mode Exit fullscreen mode

Although the logic is shared, the state is not.

Each Hook instance is isolated.


Real-World Enterprise Example

Imagine a retail banking application.

Several pages need customer information.

Instead of writing:

Dashboard

↓

API Call

↓

Profile

↓

API Call

↓

Settings

↓

API Call

Enter fullscreen mode Exit fullscreen mode

Create:

const { customer, loading } = useCustomer();

Enter fullscreen mode Exit fullscreen mode

Now every page uses the same business logic.

Benefits:

  • Less code
  • Easier maintenance
  • Consistent behavior
  • Centralized API logic

Organizing Custom Hooks

A common project structure:

src
|
├── hooks
|     |
|     ├── useAuth.js
|     ├── useFetch.js
|     ├── useWindowWidth.js
|     ├── useTheme.js
|     └── useDebounce.js
|
├── components
|
└── pages

Enter fullscreen mode Exit fullscreen mode

Keeping Hooks in a dedicated folder improves project organization.


Common Custom Hooks

Many React projects contain Hooks like:

  • useAuth()
  • useTheme()
  • useFetch()
  • useLocalStorage()
  • useDebounce()
  • usePrevious()
  • useOnlineStatus()
  • usePagination()

These Hooks encapsulate commonly used logic.


Common Mistakes

1. Not Prefixing with "use"

Incorrect:

function counter() {}

Enter fullscreen mode Exit fullscreen mode

Correct:

function useCounter() {}

Enter fullscreen mode Exit fullscreen mode

React relies on this naming convention to enforce the Rules of Hooks.


2. Calling Hooks Conditionally

Incorrect:

if (loggedIn) {
  useAuth();
}

Enter fullscreen mode Exit fullscreen mode

Hooks must always be called at the top level.

Correct:

const auth = useAuth();

if (loggedIn) {
  // Use auth here
}

Enter fullscreen mode Exit fullscreen mode


3. Putting UI Inside a Hook

Incorrect:

function useGreeting() {
  return <h1>Hello</h1>;
}

Enter fullscreen mode Exit fullscreen mode

Hooks should return:

  • State
  • Values
  • Functions

They should not return JSX.


Best Practices

  • Name every Custom Hook with the use prefix.
  • Keep Hooks focused on one responsibility.
  • Extract repeated logic into Hooks.
  • Return only what consumers need.
  • Keep UI rendering inside components.
  • Document Hook inputs and outputs for team projects.

Key Takeaways

Today, we learned:

✅ Custom Hooks let you reuse stateful logic across components.
✅ A Custom Hook is simply a function that uses React Hooks.
✅ They reduce code duplication and improve maintainability.
✅ Custom Hooks share logic, not state.
✅ Organizing reusable Hooks makes large applications easier to manage.


Coming Next 🚀

In Day 19, we will explore:

Routing in React – Building Single Page Applications with React Router

We will learn:

  • What routing is
  • Why Single Page Applications need routing
  • Installing React Router
  • Creating routes
  • Route parameters
  • Nested routes
  • Protected routes
  • Navigation with Link and useNavigate
  • Real-world authentication routing

Routing is a fundamental skill for every React developer and is used in virtually every production React application.

Happy Coding! 🚀