← Home

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

useState
const [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));
useReducer
const [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

useEffect
useEffect(() => { /* 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]);
useLayoutEffect
useLayoutEffect(() => { ... }, [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

createContext
const 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');
useContext
const 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]);
useImperativeHandle
useImperativeHandle(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

useMemo
const 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]);
useCallback
const 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} />
useTransition
const [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 />}
useDeferredValue
const 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

useActionState
const [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>
useFormStatus
const { 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>;
}
useOptimistic
const [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();
}
use
const 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 convention
function 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;
}
useLocalStorage
const [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];
}
useDebounce
const 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;
}
useFetch
const { 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 };
}
useMediaQuery
const 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;
}
useOnClickOutside
useOnClickOutside(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.

Related Resources