React Hooks Guide: useState, useEffect, useRef and More
Practice3 minInnovialabs editorial team
In short
Hooks are functions that give a component state (useState), side effects (useEffect) and references (useRef); call them only at the top level and list dependencies honestly.
What hooks are and why they matter
Hooks are functions that let a function component hold state, react to changes and work with the DOM. They replaced class components and made logic reusable: shared code moves into a custom hook instead of being scattered across lifecycle methods.
The core set you need in almost every project:
| Hook | Purpose |
|---|---|
useState | Local state; changing it triggers a re-render |
useEffect | Syncing with the outside world: subscriptions, timers, requests |
useRef | A reference to a DOM node or a value that does not trigger re-renders |
useMemo / useCallback | Caching computations and functions between renders |
useContext | Reading a context value without prop drilling |
useState: component state
const [count, setCount] = useState(0);
// When the next value depends on the previous one, pass a function
setCount(prev => prev + 1);
Key points:
- State is immutable. For objects and arrays create a copy:
setUser({ ...user, name }), notuser.name = name. - Updates are not immediate. Right after
setCount,countin the current render still holds the old value. - Expensive initial values should be passed as a function:
useState(() => parse(data))runs only once.
useEffect and the dependency array
useEffect runs code after rendering. The second argument controls when the effect re-runs:
- no array — after every render;
[]— once after mount;[userId]— wheneveruserIdchanges.
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then(r => r.json())
.then(setUser)
.catch(() => {});
return () => controller.abort(); // cleanup
}, [userId]);
The function returned from an effect is the cleanup. It runs before the next execution and on unmount. Cancel requests, remove subscriptions and clear timers there.
Common useEffect mistakes
- Missing dependencies. If the effect uses a variable, it belongs in the array. Enable the
react-hooks/exhaustive-depsESLint rule to catch this automatically. - Infinite loops. The effect updates state it depends on, or the dependencies include an object recreated on every render.
- Effects for derived data. If a value can be computed from props or state, compute it in the component body — no
useEffect, no extrauseState. - No cleanup. Subscriptions and intervals without cleanup cause leaks and duplicate handlers.
- Race conditions. Without cancellation, a response to an old request can arrive after a newer one and overwrite fresh data.
useRef: DOM access and values without re-renders
const inputRef = useRef(null);
// <input ref={inputRef} />
inputRef.current.focus();
You can mutate ref.current, and it does not trigger a re-render. That makes useRef a good fit for a timer id, a previous value or a flag that never needs to appear on screen.
The rules of hooks
- Call hooks only at the top level of a component — never inside conditions, loops or nested functions. React relies on call order.
- Call hooks only from components or other hooks, not from regular functions.
Custom hooks
A custom hook is a function whose name starts with use and that calls other hooks. It extracts repeated logic:
function useDebounce(value, delay) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}
Every component that calls the hook gets its own state — hooks share logic, not data.