React Hooks Cheat Sheet

New

useState, useEffect, useContext, useRef, useMemo, useCallback, and custom hooks

useState

Basic State

Declare state variable and its setter function

const [count, setCount] = useState(0); const [name, setName] = useState<string>("");

Functional Update

Update state based on previous value

setCount(prev => prev + 1); setItems(prev => [...prev, newItem]);

Object State

Manage object state — spread to merge updates

const [user, setUser] = useState({ name: "", age: 0 }); setUser(prev => ({ ...prev, name: "Alice" }));

useEffect

Basic Effect

Run side effects after every render

useEffect(() => { document.title = `Count: ${count}`; });

With Dependencies

Run effect only when specific values change

useEffect(() => { fetchUser(userId); }, [userId]);

On Mount Only

Run effect once when component mounts

useEffect(() => { loadData(); }, []); // empty deps = mount only

Cleanup Function

Return cleanup function to cancel subscriptions

useEffect(() => { const sub = subscribe(id); return () => sub.unsubscribe(); }, [id]);

useContext

Create Context

Create a context with a default value

const ThemeContext = createContext<"light" | "dark">("light");

Consume Context

Read context value in a component

const theme = useContext(ThemeContext);

Provide Context

Wrap components to provide context value

<ThemeContext.Provider value="dark"> <App /> </ThemeContext.Provider>

useRef

DOM Reference

Access a DOM element directly

const inputRef = useRef<HTMLInputElement>(null); <input ref={inputRef} /> inputRef.current?.focus();

Mutable Value

Store a value that persists without causing re-render

const timerRef = useRef<NodeJS.Timeout | null>(null); timerRef.current = setTimeout(fn, 1000);

useMemo & useCallback

useMemo

Memoize expensive computed values

const total = useMemo(() => { return items.reduce((sum, i) => sum + i.price, 0); }, [items]);

useCallback

Memoize callback functions to prevent child re-renders

const handleClick = useCallback((id: number) => { dispatch({ type: "DELETE", id }); }, [dispatch]);

useReducer

Basic useReducer

Manage complex state with a reducer function

type Action = { type: "INC" } | { type: "DEC" }; function reducer(state: number, action: Action): number { if (action.type === "INC") return state + 1; return state - 1; } const [count, dispatch] = useReducer(reducer, 0);

Custom Hooks

Custom Hook Pattern

Extract reusable stateful logic into a custom hook

function useCounter(initial = 0) { const [count, setCount] = useState(initial); const increment = () => setCount(c => c + 1); const reset = () => setCount(initial); return { count, increment, reset }; }

Common Patterns

Data Fetching Hook

Custom hook for async data loading with loading and error states

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    fetch(url)
      .then(r => r.json())
      .then(d => { if (!cancelled) setData(d); })
      .catch(e => { if (!cancelled) setError(e.message); })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}

Debounce Hook

Delay state updates until user stops typing

function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

// Usage
const debouncedSearch = useDebounce(searchQuery, 300);

LocalStorage Hook

Persist state to localStorage with automatic sync

function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initial;
    } catch {
      return initial;
    }
  });

  const set = (val: T) => {
    setValue(val);
    localStorage.setItem(key, JSON.stringify(val));
  };

  return [value, set] as const;
}

Tips & Best Practices

Never call hooks inside loops, conditions, or nested functions — hooks must be called at the top level

Memoize expensive computations with useMemo and stable callbacks with useCallback

Always clean up subscriptions, timers, and event listeners in the cleanup function returned from useEffect

Use the eslint-plugin-react-hooks exhaustive-deps rule to catch missing dependencies

Extract complex hook logic into named custom hooks (prefixed with use) for reusability and testability