Chapter 19 of 20 · React for Beginners — Complete Free Tutorial
Building Your Own Hooks
Two components in the React for Beginners series need the same window-width value — or the same online-status listener, the same localStorage sync. Copying a useEffect between files works, but it doubles the bug fixes. React's answer is the custom hook: extract stateful logic into a function whose name starts with use, call it from any component, and share behaviour without sharing state.
What a custom hook is
A custom hook is an ordinary function that may call other hooks. Nothing more. Its superpower is packaging: each component that calls it gets its own independent copy of the internal state, while the logic lives in one place.
import { useState } from 'react';
function useCounter(start = 0) {
const [count, setCount] = useState(start);
const increment = () => setCount((c) => c + 1);
const decrement = () => setCount((c) => c - 1);
const reset = () => setCount(start);
return { count, increment, decrement, reset };
}
Expected output: nothing on its own — hooks render nothing. Consumed as shown next, the hook behaves exactly like locally declared state.
Using a custom hook
Call it at the top of a component like any built-in hook. Two components using useCounter stay fully independent:
function LikeButton() {
const { count, increment } = useCounter(0);
return <button onClick={increment}>Likes: {count}</button>;
}
function CartSummary() {
const { count, increment, decrement } = useCounter(0);
return (
<div>
<button onClick={decrement}>-</button>
<span>Items: {count}</span>
<button onClick={increment}>+</button>
</div>
);
}
Expected output: clicking "Likes" never disturbs the cart count, and vice versa — each call to useCounter creates its own useState slot. Sharing the logic did not share the state.
A hook with an effect: useOnlineStatus
Custom hooks shine for effects. Wrapping the subscribe/cleanup pair once means every consumer gets it right:
import { useEffect, useState } from 'react';
function useOnlineStatus() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
function goOnline() { setOnline(true); }
function goOffline() { setOnline(false); }
window.addEventListener('online', goOnline);
window.addEventListener('offline', goOffline);
return () => {
window.removeEventListener('online', goOnline);
window.removeEventListener('offline', goOffline);
};
}, []);
return online;
}
// In any component:
const online = useOnlineStatus();
return online ? <p>Connected</p> : <p className="warn">Offline mode</p>;
Expected output: the indicator flips the moment the connection drops — even with dev tools' offline mode — and the listener is removed when the last consumer unmounts, because cleanup lives inside the hook.
The rules of hooks still apply
Custom hooks inherit every constraint of built-ins, which is why they are recognisable by name:
| Rule | Why |
|---|---|
| Call hooks only at the top level of a component or hook | React relies on a consistent call order every render |
| Call them the same way every render | Conditionals or loops shift the order and corrupt state |
Name the function useSomething | ESLint's rules-of-hooks plugin can then check your code |
| Only call from components or other hooks | Nowhere else has React's render context |
Designing a good hook
- One concern per hook —
useOnlineStatusdoes one thing; a hook that fetches, validates and navigates is three hooks. - Return what callers need — a value, or an object like
{ count, increment }; document the shape in a comment or TypeScript later. - Accept configuration as arguments — defaults, ids, intervals: parameters keep the hook general.
- Compose hooks — a hook may call other hooks;
usePaginatedFetchcan build on auseFetch. - Keep side effects inside effects — a hook that sets state during render is a bug, not a feature.
Custom hook vs utility function
The deciding test: does it use hooks? Pure transformations — formatting dates, sorting arrays, calculating totals — belong in plain modules where they are testable without React. If the function calls useState, useEffect, useContext or another hook, it must be a custom hook.
Common mistakes
- Conditional hook calls —
if (ready) { const v = useThing(); }breaks React; restructure instead. - Forgetting the
useprefix — ESLint cannot enforce hook rules ongetStatus(), and reviewers miss it too. - Sharing state unintentionally — module-level variables inside a hook leak between consumers; keep state in hooks, config in arguments.
- Missing cleanup — every listener, timer or subscription added in the hook needs its removal in the returned cleanup.
- God hooks — one hook doing five jobs forces every caller to pay for all five; split it.
Practice checklist
- Extract your earlier counter logic into
useCounterand use it in two components; verify independence. - Build
useOnlineStatusand test with the network tab's offline toggle. - Write
useLocalStorage(key, initial)that syncs state to storage — note the lazy initialiser. - Create a hook with a deliberate conditional call, read the ESLint error, then fix the structure.
- Refactor one duplicated effect from your earlier chapters into a shared hook.
Your hooks are production-shaped — finish the series by surviving failures and going live: Error Boundaries and Deployment.
Frequently Asked Questions
React is a JavaScript library for building user interfaces from reusable components. It manages how your UI updates when data changes, so you describe what the screen should look like and React performs the efficient DOM updates. It is one of the most in-demand frontend skills and powers products from Netflix to Instagram, which makes it an excellent next step after core JavaScript.
You should be comfortable with functions, arrays and objects (map, filter, spread), destructuring, modules and basic async/await. React itself is written in plain JavaScript — JSX is a syntax layer on top. If any of those feel shaky, revisit the JavaScript fundamentals first and this series will go much faster.
React is a library focused only on the UI layer — you choose your own router, data layer and tooling. Angular is a full framework that ships routing, forms, HTTP client and testing conventions as one opinionated package. React's flexibility and huge ecosystem make it the common choice for teams that want to compose their own stack.
A component is a reusable function that returns UI. It receives input through props, keeps private data in state, and can render other components. Pages are composed by nesting small components (navbar, card list, footer) instead of one giant file, which keeps code testable and easy to change.
JSX is a syntax extension that looks like HTML but compiles to React's create() calls — it is a description of UI, not markup the browser parses. Small differences matter: class becomes className, style takes an object, and you embed JavaScript expressions inside curly braces. Once compiled, React turns that description into real DOM nodes.
Props are inputs passed from a parent — they are read-only inside the child, like function arguments. State is private, mutable data owned by a component; updating it triggers a re-render. The rule of thumb: values that come from outside the component are props; values that change over the component's lifetime are state.
Rendering means calling your component function to produce a description of the UI for the current state and props. It does not always mean DOM work — React compares the new description with the previous one (reconciliation) and applies only the differences to the real DOM. This is why updating state 100 times does not mean 100 full page rebuilds.
An effect runs after the component has committed to the screen — after render, not during it. You list dependencies in an array, and React re-runs the effect only when one of them changes. If the effect returns a cleanup function, React runs it before the next re-run and on unmount, which is where you cancel timers, abort fetches and remove listeners.
Use Vite. Create React App is effectively in maintenance mode, while Vite starts instantly (native ES modules) and builds fast, and it is the default choice for new React projects. This series uses Vite for every example — you only need Node.js installed to follow along.
React apps compile to static files (HTML, CSS and JavaScript), so any static host works: Netlify, Vercel, GitHub Pages or Cloudflare Pages. Run the production build, upload the dist folder, and configure a single-page-application fallback so unknown paths load index.html — routing chapters explain why that fallback is required.