Custom Hooks
Custom hooks extract reusable stateful logic into named use-prefixed functions, sharing behavior without wrapper components. They replace HOCs and render props but require strict adherence to the Rules of Hooks and careful effect dependencies.
A custom hook is a JavaScript function whose name starts with use and that calls other hooks to encapsulate reusable, stateful logic. Custom hooks became the standard way to share behavior in React after Hooks shipped, replacing most uses of higher-order components and render props because they reuse logic without adding nodes to the component tree.
How It Works
A custom hook composes built-in hooks like useState, useEffect, useRef, and useContext, then returns whatever the caller needs — values, setters, or handlers. Calling the same hook from two components gives each its own isolated state; hooks share logic, not state instances. They follow the Rules of Hooks: call them only at the top level of components or other hooks, and only from React functions, so call order stays stable across renders.
For example useFetch(url) can hold data, loading, and error state, run the request in an effect, cancel on unmount, and return the trio. Any component calling it gets that lifecycle for free, with no wrapper component and no hidden props.
When to Use It
Extract a custom hook whenever two or more components need the same stateful behavior — data fetching, form state, subscriptions, timers, media queries, debouncing, or local-storage sync. Hooks also clean up a single complex component by grouping related effects and state behind an intention-revealing name. Migrating class components to function components nearly always means converting lifecycle logic into custom hooks.
Trade-offs
Hooks demand discipline: violating the Rules of Hooks (conditional or looped calls) causes subtle bugs, and dependency arrays in useEffect are a common source of stale-closure and over-firing errors. Over-extraction can scatter logic across many tiny hooks that are hard to follow. Hooks also do not solve cross-tree data sharing on their own — that still needs context or a store. Linting (eslint-plugin-react-hooks) is effectively mandatory.
Related Patterns
Custom hooks are the modern replacement for higher-order components and render props. They pair with the provider pattern to consume shared context, and they often serve as the data layer behind a container/presentational split.
Example
function useFetch(url) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let active = true;
setLoading(true);
fetch(url).then(r => r.json())
.then(d => active && (setData(d), setLoading(false)))
.catch(e => active && (setError(e), setLoading(false)));
return () => { active = false; };
}, [url]);
return { data, error, loading };
}
The fetch lifecycle is reusable across components with no wrapper nesting and no obscured prop origins.