Quick reference for core and advanced React hooks with usage rules and patterns.
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;useEffect(() => {
fetch("/api/data").then(r => r.json()).then(setData);
return () => { /* cleanup */ };
}, []);const { theme } = useContext(ThemeContext);
return <div style={{ background: theme }}>Content</div>;const [state, dispatch] = useReducer(reducer, { count: 0 });
return <button onClick={() => dispatch({ type: "INCREMENT" })}>
{state.count}
</button>;const inputRef = useRef(null);
return (
<>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</>
);const handleClick = useCallback(() => {
doSomething(value);
}, [value]);const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);const handleSubmit = useCallback((data) => {
saveData(data);
}, []); // stable reference — MemoizedChild won't re-render
return <MemoizedChild onSubmit={handleSubmit} />;const sortedItems = useMemo(
() => [...items].sort((a, b) => a.name.localeCompare(b.name)),
[items]
);// Add these only after profiling shows a real bottleneck
// Premature optimization adds complexity without benefitconst inputRef = useRef(null);
return (
<>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</>
);useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
clear: () => { inputRef.current.value = ""; }
}));const timerRef = useRef(null);
function start() { timerRef.current = setInterval(tick, 1000); }
function stop() { clearInterval(timerRef.current); }const prevCountRef = useRef(count);
useEffect(() => { prevCountRef.current = count; }, [count]);
const prevCount = prevCountRef.current; // value from last renderconst Input = forwardRef((props, ref) => (
<input ref={ref} {...props} />
));useLayoutEffect(() => {
// Runs before browser paints — safe to read DOM measurements here
const height = elementRef.current.offsetHeight;
setHeight(height);
}, []);function useCustomHook(value) {
useDebugValue(value > 10 ? "large" : "small");
return value;
}const [isPending, startTransition] = useTransition();
startTransition(() => {
setSearchResults(results); // won't block input from being typed
});const deferredQuery = useDeferredValue(searchQuery);
const results = useMemo(() => filterList(deferredQuery), [deferredQuery]);function Input({ label }) {
const id = useId();
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} />
</>
);
}// Good
function Component() {
const [count, setCount] = useState(0);
useEffect(() => { /* ... */ }, []);
}
// Bad: hook inside condition
if (condition) {
const [count, setCount] = useState(0); // Wrong!
}{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}// Good: React treats useWindowWidth as a hook
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handler = () => setWidth(window.innerWidth);
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
}, []);
return width;
}// React tracks hooks by call order — conditional calls break this
// Move conditions inside the hook body, not around it
useEffect(() => {
if (!isLoggedIn) return; // condition inside — safe
}, [isLoggedIn]);Pass a dependency array as the second argument to useEffect — an empty array [] runs the effect once on mount, while listing specific values like [userId] re-runs only when those values change. Avoid placing the fetch URL or options object directly in the dependency array if they're recreated each render, as object identity changes will trigger the effect repeatedly.
useState triggers a re-render when updated, while useRef stores a mutable value in .current that persists across renders without causing re-renders. Use useRef for values you need to track (like timers, previous values, or DOM nodes) but don't want to drive the UI.
Use them only after profiling confirms a real performance problem — wrapping every function or computed value adds overhead and complexity that often outweighs any benefit. The most justified cases are passing stable callbacks to heavily optimized child components (React.memo) or skipping expensive recalculations in tight render loops.
React 18 Strict Mode intentionally double-invokes effects in development to surface bugs in effects that don't properly clean up. Return a cleanup function from your effect to handle unmounting correctly, and your production build will only run the effect once.
Extract the state and effects into a custom hook — a plain JavaScript function whose name starts with 'use' that calls other hooks internally. Each component that calls the custom hook gets its own isolated state, so sharing logic doesn't mean sharing state.