React Hooks Cheatsheet
Every React hook with syntax, description, and examples — from useState and useEffect to React 19 hooks like useActionState, useOptimistic, and use. Includes custom hook patterns.
State Hooks
useStateconst [state, setState] = useState(initialValue)Declares a state variable. Returns the current value and a setter function. Re-renders the component when state changes.const [count, setCount] = useState(0); setCount(prev => prev + 1);
useState (object)const [form, setForm] = useState({ name: '', email: '' })State can hold any value — objects, arrays, primitives. When updating objects, always spread the previous state since setState replaces (not merges).setForm(prev => ({ ...prev, name: 'Ada' }));useState (lazy init)const [val, setVal] = useState(() => expensiveCompute())Pass a function to useState for expensive initial values. The function runs only on the first render, not on re-renders.const [rows, setRows] = useState(() => parseCSV(data));
useReducerconst [state, dispatch] = useReducer(reducer, initialState)Alternative to useState for complex state logic. Takes a reducer function (state, action) → newState and an initial state.function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: 'increment' });useReducer (init fn)useReducer(reducer, initialArg, init)Third argument is a lazy initializer: init(initialArg) computes the initial state. Useful to reset state or derive it from props.const init = (count) => ({ count });
useReducer(reducer, 0, init);Effect Hooks
useEffectuseEffect(() => { /* effect */ return () => { /* cleanup */ } }, [deps])Runs side effects after render. The cleanup function runs before the next effect and on unmount. Fires asynchronously after paint.useEffect(() => {
const id = setInterval(() => tick(), 1000);
return () => clearInterval(id);
}, []);useEffect (no deps)useEffect(() => { ... })Without a dependency array, the effect runs after every render. Use sparingly — this is rarely what you want.useEffect(() => {
document.title = `Count: ${count}`;
});useEffect (empty deps)useEffect(() => { ... }, [])An empty dependency array means the effect runs once on mount and the cleanup runs on unmount. Equivalent to componentDidMount + componentWillUnmount.useEffect(() => {
fetchUser(id).then(setUser);
}, []);useEffect (with deps)useEffect(() => { ... }, [a, b])The effect re-runs whenever any dependency value changes (compared with Object.is). List every reactive value the effect reads.useEffect(() => {
const conn = createConnection(url);
conn.connect();
return () => conn.disconnect();
}, [url]);useLayoutEffectuseLayoutEffect(() => { ... }, [deps])Identical API to useEffect but fires synchronously after DOM mutations, before the browser paints. Use for DOM measurements or to prevent visual flicker.useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setHeight(height);
}, []);Context Hooks
createContextconst MyCtx = createContext(defaultValue)Creates a Context object. The defaultValue is used when a component reads context but has no matching Provider above it in the tree.const ThemeCtx = createContext('light');useContextconst value = useContext(MyCtx)Reads and subscribes to a context. Re-renders the component whenever the context value changes. Must be called with the Context object, not the Provider.const theme = useContext(ThemeCtx);
return <div className={theme}>...</div>;Provider pattern<MyCtx.Provider value={val}>...</MyCtx.Provider>Wrap a subtree with a Provider to pass a value down. All descendants calling useContext(MyCtx) will receive this value.function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeCtx.Provider value={theme}>
<Page />
</ThemeCtx.Provider>
);
}Ref Hooks
useRef (DOM)const ref = useRef(null)Creates a mutable ref object whose .current property persists across renders. Commonly used to access DOM elements directly.const inputRef = useRef(null);
<input ref={inputRef} />
inputRef.current.focus();useRef (mutable value)const ref = useRef(initialValue)Unlike state, mutating ref.current does not trigger a re-render. Useful for storing timers, previous values, or any mutable data that doesn't affect the UI.const prevCount = useRef(count);
useEffect(() => {
prevCount.current = count;
}, [count]);useImperativeHandleuseImperativeHandle(ref, () => ({ method() {} }), [deps])Customizes the value exposed to parent components when using ref with forwardRef. Restricts the parent to a specific API instead of the full DOM node.const MyInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
scrollIntoView: () => inputRef.current.scrollIntoView(),
}));
return <input ref={inputRef} />;
});Performance Hooks
useMemoconst value = useMemo(() => compute(a, b), [a, b])Caches the result of a computation between re-renders. Only recomputes when dependencies change. Use for expensive calculations, not for every variable.const sorted = useMemo( () => items.toSorted((a, b) => a.name.localeCompare(b.name)), [items] );
useMemo (pitfalls)useMemo(() => value, [dep])Don't memoize cheap computations — the overhead of useMemo itself can be worse. React may discard cached values to free memory. Never rely on memoization for correctness.// ❌ Unnecessary — simple math is cheap const double = useMemo(() => count * 2, [count]); // ✅ Useful — filtering a large list const results = useMemo(() => bigList.filter(matchQuery), [bigList, query]);
useCallbackconst fn = useCallback((args) => { ... }, [deps])Caches a function definition between re-renders. Returns the same function reference as long as deps haven't changed. Equivalent to useMemo(() => fn, deps).const handleClick = useCallback((id) => {
setItems(prev => prev.filter(item => item.id !== id));
}, []);
<MemoizedChild onClick={handleClick} />useTransitionconst [isPending, startTransition] = useTransition()Marks a state update as non-urgent (a transition). React can interrupt the transition to handle urgent updates (like typing). isPending indicates if the transition is ongoing.const [isPending, startTransition] = useTransition();
function handleSearch(query) {
startTransition(() => {
setSearchResults(filterLargeList(query));
});
}
{isPending && <Spinner />}useDeferredValueconst deferred = useDeferredValue(value)Defers updating a value until more urgent work completes. The deferred value lags behind the actual value during transitions. Useful for debouncing expensive re-renders.const [query, setQuery] = useState('');
const deferred = useDeferredValue(query);
<input onChange={e => setQuery(e.target.value)} />
<HeavyList query={deferred} />React 19 Hooks
useActionStateconst [state, formAction, isPending] = useActionState(action, initialState)Manages form state with a server or client action. The action receives previous state + formData and returns new state. isPending is true while the action is running.async function submitForm(prevState, formData) {
const error = await saveUser(formData);
return error ? { message: error } : { message: 'Saved!' };
}
const [state, formAction, isPending] = useActionState(submitForm, { message: '' });
<form action={formAction}>...</form>useFormStatusconst { pending, data, method, action } = useFormStatus()Returns the status of the parent <form>. Must be called from a component rendered inside a <form>. pending is true while the form action is executing.function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Saving...' : 'Save'}</button>;
}useOptimisticconst [optimistic, addOptimistic] = useOptimistic(state, updateFn)Shows an optimistic (temporary) state while an async action is pending. Reverts to the real state when the action completes or fails.const [optimisticLikes, addLike] = useOptimistic(
likes,
(current, newLike) => [...current, newLike]
);
async function handleLike() {
addLike({ id: tempId, name: 'You' });
await likeMutation();
}useconst value = use(resource)Reads a resource (Promise or Context) during render. Unlike useContext, use() can be called conditionally. With Promises, it integrates with Suspense.// Reading a promise (with Suspense)
function Comments({ commentsPromise }) {
const comments = use(commentsPromise);
return comments.map(c => <p key={c.id}>{c.text}</p>);
}
// Reading context conditionally
if (showTheme) {
const theme = use(ThemeCtx);
}Custom Hooks
Naming conventionfunction useMyHook(...) { ... }Custom hooks must start with 'use' followed by a capital letter. This lets React enforce the Rules of Hooks via the linter. They can call any other hooks inside.function useWindowWidth() {
const [w, setW] = useState(window.innerWidth);
useEffect(() => {
const h = () => setW(window.innerWidth);
window.addEventListener('resize', h);
return () => window.removeEventListener('resize', h);
}, []);
return w;
}useLocalStorageconst [value, setValue] = useLocalStorage(key, initial)Syncs state with localStorage. Reads the stored value on mount and writes to localStorage whenever the value changes.function useLocalStorage(key, initial) {
const [val, setVal] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initial;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(val));
}, [key, val]);
return [val, setVal];
}useDebounceconst debounced = useDebounce(value, delay)Returns a debounced version of a value that only updates after the specified delay has elapsed since the last change.function useDebounce(value, delay) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}useFetchconst { data, error, loading } = useFetch(url)Encapsulates data fetching with loading and error states. Handles cleanup to avoid setting state on unmounted components.function useFetch(url) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
fetch(url).then(r => r.json())
.then(d => { if (!cancelled) setData(d); })
.catch(e => { if (!cancelled) setError(e); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [url]);
return { data, error, loading };
}useMediaQueryconst matches = useMediaQuery(query)Tracks whether a CSS media query matches. Updates reactively when the viewport changes.function useMediaQuery(query) {
const [matches, setMatches] = useState(
() => window.matchMedia(query).matches
);
useEffect(() => {
const mql = window.matchMedia(query);
const handler = (e) => setMatches(e.matches);
mql.addEventListener('change', handler);
return () => mql.removeEventListener('change', handler);
}, [query]);
return matches;
}useOnClickOutsideuseOnClickOutside(ref, handler)Calls a handler when a click occurs outside the referenced element. Commonly used for closing dropdowns, modals, and popovers.function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = (e) => {
if (!ref.current || ref.current.contains(e.target)) return;
handler(e);
};
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
}, [ref, handler]);
}FAQ
What are the Rules of Hooks?
There are two core rules: (1) Only call hooks at the top level — never inside loops, conditions, or nested functions. This ensures hooks are called in the same order every render. (2) Only call hooks from React function components or custom hooks — not from regular JavaScript functions. The eslint-plugin-react-hooks enforces both rules automatically.
When should I use useEffect vs handling logic in event handlers?
Use event handlers for logic triggered by user actions (clicks, form submissions, etc.). Use useEffect only for synchronizing with external systems — subscriptions, timers, DOM APIs, third-party libraries. If something can be computed during render, do it during render. Effects that run 'because a value changed' are often better handled by deriving the value or using an event handler.
What is the difference between useMemo and useCallback?
useMemo caches the result of a computation: useMemo(() => compute(), [deps]) returns the computed value. useCallback caches the function itself: useCallback(fn, [deps]) returns fn. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). Use useMemo for expensive calculations and useCallback for stable function references passed to memoized children.
What are the best practices for writing custom hooks?
Start the name with 'use' followed by a capital letter (useAuth, not useauth). Keep custom hooks focused on one concern. Return only the values consumers need — consider returning an object for named access or a tuple for positional destructuring. Always include cleanup functions for subscriptions and timers. Document the hook's contract: what it accepts, what it returns, and any side effects.