Hooks, Effects & External Systems
Learn the Rules of Hooks, effect lifecycle, dependency reasoning, refs, memoization hooks, custom hooks, and how to avoid effect-driven design problems.
Rules of Hooks
Hooks such as useState, useEffect, and useMemo must be called at the top level of React function components or custom hooks. Do not call hooks inside loops, conditions, nested functions, or arbitrary utility functions.
React relies on a consistent hook call order to associate hook state with a component render. Conditional hook calls change that order and break the association.
Custom hooks are functions whose names begin with use and that can call other hooks. They package reusable stateful behavior without sharing component state implicitly.
function useOnlineStatus() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
const onOnline = () => setOnline(true);
const onOffline = () => setOnline(false);
window.addEventListener("online", onOnline);
window.addEventListener("offline", onOffline);
return () => {
window.removeEventListener("online", onOnline);
window.removeEventListener("offline", onOffline);
};
}, []);
return online;
}- Hooks require stable call order.
- Custom hooks share behavior, not state instances.
- A custom hook should have a focused contract.
useEffect: Synchronizing with External Systems
An Effect is for synchronizing a component with something outside React: subscriptions, timers, browser APIs, imperative widgets, or network interactions when appropriate.
Do not use an Effect merely because a value can be calculated from other state. If fullName is derived from firstName and lastName, calculate it during render. An unnecessary effect creates an extra render and introduces synchronization complexity.
An effect has a setup phase and optionally a cleanup phase. Cleanup should undo the external subscription, timer, listener, or resource established by the setup.
useEffect(() => {
const connection = connect(roomId);
connection.on("message", handleMessage);
return () => {
connection.off("message", handleMessage);
connection.disconnect();
};
}, [roomId, handleMessage]);- Effects synchronize with external systems.
- Cleanup must reverse setup work.
- Do not use effects for ordinary derivations.
Which is the best reason to use `useEffect`?
Effect Dependencies & Stale Closures
The dependency array describes reactive values used by the effect. If an effect reads a prop or state value, that value generally belongs in its dependency list unless the design intentionally uses another mechanism.
A stale closure occurs when a callback retains values from an older render. This commonly appears with timers, subscriptions, or asynchronous callbacks. The solution is not always to add an empty dependency array. Instead, decide what the effect should synchronize and ensure the callback sees the intended values.
The exhaustive-deps lint rule is useful because missing dependencies often indicate a design problem. When satisfying it causes a loop, reconsider the effect rather than suppressing the warning blindly.
[] means the effect does not depend on reactive values from the component. It should not be used as a general-purpose 'run once' escape hatch.
- Dependencies describe reactive inputs to an effect.
- Stale closures are often symptoms of unclear synchronization design.
- Avoid suppressing dependency warnings without understanding the lifecycle.
useRef: Mutable Values & DOM References
useRef stores a mutable value that persists across renders without causing a re-render when changed. It is also commonly used to reference DOM nodes.
Use refs for values that do not belong in the rendered UI: timer IDs, imperative handles, previous values, or DOM nodes. If changing the value should update the screen, it usually belongs in state instead.
A ref's .current is mutable. Mutating it during render can make code difficult to reason about and should generally be avoided unless you have a very specific, well-understood pattern.
function SearchInput() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current?.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>Focus</button>
</>
);
}- Refs persist values without triggering renders.
- Use state for values that affect rendered output.
- Refs are useful for DOM access and imperative integrations.
useMemo, useCallback & React.memo
Memoization can avoid repeated work or preserve referential identity, but it is not a default requirement. First make the code correct; then profile actual bottlenecks.
useMemo caches a calculated value. useCallback caches a function identity. React.memo can skip rendering a component when its props are considered equal. These techniques work together when a stable prop identity enables a child to skip expensive work.
Memoization can also make code harder to understand and consume memory. A memoized computation that is cheaper than maintaining the cache is often a net loss.
const visibleProducts = useMemo(
() => products.filter(p => matches(p, query)),
[products, query]
);- Memoization is an optimization, not a correctness mechanism.
- Profile before adding complex memoization.
- Stable identity matters when consumers compare references.
Designing High-Quality Custom Hooks
A custom hook should hide implementation details while exposing a small, predictable API. For example, useDebouncedValue(value, delay) should not force callers to understand timers, cleanup, or dependency management.
Good hooks separate reusable behavior from UI. A hook may return state, actions, status flags, and refs. Avoid hooks that return a huge object containing every internal detail of a feature.
Custom hooks can compose other hooks. They are a major reason React's function-component model scales beyond simple components.
function useDebouncedValue<T>(value: T, delay: number) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}- Custom hooks should expose behavior, not implementation.
- Keep hook APIs small and predictable.
- Hooks can compose state, effects, refs, and other hooks.