React Mastery / Custom Hooks

Introduction to Custom Hooks

Custom hooks are the secret sauce of React development. They allow you to extract component logic into reusable functions, keeping your components lean and your codebase DRY (Don't Repeat Yourself).

Think of them as a way to "package" complex stateful logic so you can use it anywhere in your app without copy-pasting code.


❓What is a Custom Hook?

A custom hook is a JavaScript function whose name starts with "use" and that may call other hooks.

  • The Goal: Share logic between components, not state itself. Each call to a custom hook gets a completely isolated state.

  • The Requirement: You must follow the Rules of Hooks (e.g., only call hooks at the top level).

Why use them?

  1. Readability: Hide complex logic behind a descriptive name like useAuth or useFetch.

  2. Reusability: Write once, use in multiple components.

  3. Testability: You can test the logic of the hook independently of the UI.


👀 Anatomy of a Custom Hook

A custom hook typically follows this pattern:

  1. Define the function (starting with use).

  2. Use built-in hooks (useState, useEffect, etc.) inside.

  3. Return what the component needs (values, functions, or objects).

Example: useWindowWidth

Suppose you want to track the browser window size in multiple components.

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    
    // Cleanup the listener on unmount
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return width;
}

🧠 Code Walkthrough

1. Initializing State

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

We initialize the state with the actual current width of the window. This ensures that the very first time a component uses this hook, it has a real value to display rather than null or 0.

2. The Side Effect (useEffect)

The "magic" happens inside useEffect. We need to tell the browser: "Hey, every time the window changes size, let me know."

  • The Listener: const handleResize = () => setWidth(window.innerWidth);

    This function runs every time the event triggers, updating our React state with the new width.

  • The Attachment: window.addEventListener('resize', handleResize);

    This hooks our function into the browser's native resize event.

  • The Dependency Array []: By passing an empty array, we ensure this setup only happens once when the component (using the hook) mounts.

3. The Cleanup (Crucial!)

return () => window.removeEventListener('resize', handleResize);

Custom hooks that use subscriptions or event listeners must clean up after themselves. If you don't remove the listener, it stays in the browser's memory even after the component is gone, leading to memory leaks and performance issues.

4. The Return Value

return width;

The hook returns the raw number. The component using it doesn't care how the width is calculated; it just wants the number.

How to Use It in a Component

Once defined, using it is incredibly simple:

function MyResponsiveComponent() {
  const width = useWindowWidth(); // Simple!

  return (
    <div>
      <p>Window width is: {width}px</p>
      {width < 600 ? <MobileView /> : <DesktopView />}
    </div>
  );
}

✏️ Implementation Workflow

When building your own, follow these steps:

Step

Action

Why?

1. Identify

Find logic that repeats or is too bulky for a component.

Keeps components focused on UI.

2. Extract

Move that logic into a new function named useSomething.

"use" prefix lets React check for hook rule violations.

3. Generalize

Use arguments to make the hook flexible (e.g., passing a URL to a fetch hook).

Increases reusability.

4. Return

Decide if you need to return an array [val, fn] or an object { val, fn }.

Arrays are good for renaming; objects are better for many values.


✅ Common Patterns & Best Practices

  • Return Objects for Clarity: If your hook returns more than two values, use an object. This prevents users from having to remember the exact order of elements in an array.

  • Keep it Pure-ish: Hooks should manage logic, not side effects that affect the global window unless necessary.

  • Compose Hooks: You can call custom hooks inside other custom hooks! This allows for building highly specialized logic from small, building-block hooks.


↔️ Quick Comparison: Component vs. Hook

Feature

Component

Custom Hook

Purpose

Returns UI (JSX)

Returns Data/Logic

Naming

PascalCase (UserProfile)

camelCase with "use" (useAuth)

State

Holds UI state

Manages logic state


Note: Remember that custom hooks don't share state. If ComponentA and ComponentB both use useCounter, they each get their own independent count variable.

👉 In the upcoming chapters, we will build some of the most commonly used custom hooks.