Custom Hooks

Introduction

A custom hook is a function named useSomething that calls built-in hooks to encapsulate reusable stateful logic. Share useCounter, useFetch, and useLocalStorage across components without copy-paste or deep prop chains.

Prerequisites

Rules of Hooks

RuleMeaning
Top level onlyDo not call hooks inside loops, conditions, or nested functions
React functions onlyCall hooks from components or other custom hooks
Stable orderSame hooks in the same order every render

ESLint eslint-plugin-react-hooks enforces these rules.

Unlike Vue composables, React hooks cannot be called conditionally—see Vue Composables comparison.

useCounter

src/hooks/useCounter.ts:

CounterPanel.tsx:

useFetch

src/hooks/useFetch.ts:

Usage:

tsx
const { data, loading, error } = useFetch<Health>("/api/health");

Full API patterns in Fetching API and CORS—blog project uses usePosts / usePost.

useLocalStorage

Warning

Sensitive Data

Do not store JWT in localStorage for high-security apps without understanding XSS—see admin auth project.

Testing Hooks

With @testing-library/react:

tsx
import { renderHook, act } from "@testing-library/react";
import { useCounter } from "./useCounter";
 
test("increments", () => {
  const { result } = renderHook(() => useCounter(0));
  act(() => result.current.increment());
  expect(result.current.count).toBe(1);
});

Details in Testing React Components.

File Layout

text
src/hooks/
├── useCounter.ts
├── useFetch.ts
├── usePosts.ts      # later — blog project
└── useLocalStorage.ts

FAQ

Hook vs utility function?

If it uses useState, useEffect, etc.—it must be a hook (use prefix).

Pass parameters?

tsx
export function usePost(slug: string) { ... }

Re-run effects when slug changes.

Share hook state between components?

Each call creates independent state. For shared state use Zustand or Context.

Extract hook from component?

Select lines using hooks → move to useXxx.ts → return values and handlers.

Next chapter?

Styling React Components.