Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

React Mastery Series – Day 18: Custom Hooks in React – Reusing Logic Like a Pro

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 em

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
  };
}

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

const { count, increment } = useCounter();

Why Do We Need Custom Hooks?

Consider two components.

User Dashboard

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

Admin Dashboard

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

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
  };

}

Using it:

function Counter() {

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

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

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

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

}

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
  };

}

Now multiple components can simply do:

const {users, loading} = useUsers();

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;
}

Usage:

const width = useWindowWidth();

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;
}

Usage:

const isOnline = useOnlineStatus();

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

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();

Result:

Counter 1 → 5

Counter 2 → 12

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

Create:

const { customer, loading } = useCustomer();

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

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() {}

Correct:

function useCounter() {}

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

2. Calling Hooks Conditionally

Incorrect:

if (loggedIn) {
  useAuth();
}

Hooks must always be called at the top level.

Correct:

const auth = useAuth();

if (loggedIn) {
  // Use auth here
}

3. Putting UI Inside a Hook

Incorrect:

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

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! 🚀

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.