React Hooks Cheatsheet
reactQuick reference for all React hooks with syntax and examples.
State Management Hooks
const [state, setState] = useState<T>(initialState: T | (() => T)): [T, Dispatch<SetStateAction<T>>]Declares a state variable that triggers re-renders when updated
const [count, setCount] = useState<number>(0);
setCount(prev => prev + 1);const [user, setUser] = useState<User>({ name: '', age: 0 })Managing object state requires spreading previous state for partial updates
const [form, setForm] = useState({ name: '', email: '' });
setForm(prev => ({ ...prev, name: 'John' }));const [state, setState] = useState<T>(() => expensiveComputation())Pass a function to compute initial state only once on mount
const [data, setData] = useState(() => JSON.parse(localStorage.getItem('data') || '{}'));const [state, dispatch] = useReducer<R>(reducer: R, initialState: ReducerState<R>): [ReducerState<R>, Dispatch<ReducerAction<R>>]Manages complex state logic with a reducer function similar to Redux
const reducer = (state, action) => action.type === 'increment' ? { count: state.count + 1 } : state;
const [state, dispatch] = useReducer(reducer, { count: 0 });const [state, dispatch] = useReducer(reducer, initialArg, init)Lazy initialization for useReducer using an init function
const init = (initialCount) => ({ count: initialCount });
const [state, dispatch] = useReducer(reducer, 0, init);Effect Hooks
useEffect(effect: EffectCallback, deps?: DependencyList): voidRuns side effects after render, with optional cleanup and dependency tracking
useEffect(() => {
document.title = `Count: ${count}`;
return () => console.log('cleanup');
}, [count]);useEffect(() => { /* runs once */ }, [])Empty dependency array ensures effect runs only on component mount
useEffect(() => {
fetchData().then(setData);
}, []);useEffect(() => { return () => cleanup(); }, [deps])Return a cleanup function to run before effect re-runs or on unmount
useEffect(() => {
const id = setInterval(() => tick(), 1000);
return () => clearInterval(id);
}, []);useLayoutEffect(effect: EffectCallback, deps?: DependencyList): voidFires synchronously after DOM mutations but before browser paint
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setHeight(height);
}, []);useInsertionEffect(effect: EffectCallback, deps?: DependencyList): voidFires before DOM mutations for CSS-in-JS library style injection
useInsertionEffect(() => {
const style = document.createElement('style');
document.head.appendChild(style);
}, []);Context Hook
const value = useContext<T>(Context: React.Context<T>): TSubscribes to a React context and returns its current value
const ThemeContext = createContext<'light' | 'dark'>('light');
const theme = useContext(ThemeContext);<Context.Provider value={value}>{children}</Context.Provider>Wrap components with Provider to pass context value down the tree
const UserContext = createContext<User | null>(null);
<UserContext.Provider value={user}>{children}</UserContext.Provider>const useTheme = () => useContext(ThemeContext)Create a custom hook for cleaner context consumption
const useAuth = () => {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('Must be within AuthProvider');
return ctx;
};Performance Optimization Hooks
const memoizedValue = useMemo<T>(() => computeValue(): T, deps: DependencyList): TMemoizes expensive computations and recalculates only when dependencies change
const sortedList = useMemo(() =>
items.sort((a, b) => a.name.localeCompare(b.name)),
[items]);const memoizedCallback = useCallback<T>(callback: T, deps: DependencyList): TReturns a memoized callback that only changes when dependencies change
const handleClick = useCallback((id: string) => {
setSelected(id);
}, []);useCallback(fn, deps) === useMemo(() => fn, deps)useCallback memoizes functions while useMemo memoizes any computed value
const fn = useCallback(() => doSomething(a, b), [a, b]);
const fn2 = useMemo(() => () => doSomething(a, b), [a, b]);const options = useMemo(() => ({ sortBy, filterBy }), [sortBy, filterBy])Preserve object reference to prevent unnecessary child re-renders
const config = useMemo(() => ({
theme: 'dark',
locale: userLocale
}), [userLocale]);Ref Hooks
const ref = useRef<T>(initialValue: T): MutableRefObject<T>Creates a mutable ref object that persists across renders without causing re-renders
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();const ref = useRef<HTMLElement>(null)Access DOM nodes directly by attaching ref to JSX elements
const divRef = useRef<HTMLDivElement>(null);
<div ref={divRef}>Content</div>const valueRef = useRef<T>(value)Store mutable values that dont trigger re-renders when changed
const renderCount = useRef(0);
useEffect(() => { renderCount.current += 1; });const prevRef = useRef<T>()Track previous prop or state values across renders
const prevCount = useRef(count);
useEffect(() => { prevCount.current = count; }, [count]);useImperativeHandle<T, R>(ref: Ref<T>, createHandle: () => R, deps?: DependencyList): voidCustomizes the instance value exposed to parent when using forwardRef
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => inputRef.current.value = ''
}), []);Concurrent Rendering Hooks
const [isPending, startTransition] = useTransition(): [boolean, TransitionStartFunction]Marks state updates as non-urgent allowing UI to remain responsive
const [isPending, startTransition] = useTransition();
startTransition(() => setSearchResults(filterData(query)));const deferredValue = useDeferredValue<T>(value: T): TDefers updating a value to keep UI responsive during expensive re-renders
const deferredQuery = useDeferredValue(query);
const results = useMemo(() => search(deferredQuery), [deferredQuery]);isPending ? <Spinner /> : <Content />Use isPending to show loading indicators during transitions
const [isPending, startTransition] = useTransition();
return isPending ? <Loading /> : <List items={items} />;useDeferredValue(value) // for values from props
startTransition(() => setState()) // for state you controlUse useDeferredValue for values you dont control, useTransition for state updates
// Props: const deferred = useDeferredValue(propValue);
// State: startTransition(() => setFilter(newFilter));Utility Hooks
const id = useId(): stringGenerates unique IDs stable across server and client for accessibility attributes
const id = useId();
<label htmlFor={id}>Name</label>
<input id={id} type="text" />const id = useId(); // use as prefixUse a single useId call as prefix for multiple related elements
const id = useId();
<input id={`${id}-name`} />
<input id={`${id}-email`} />useDebugValue<T>(value: T, format?: (value: T) => any): voidDisplays a label for custom hooks in React DevTools
function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}useDebugValue(date, date => date.toISOString())Pass a format function to defer expensive formatting until DevTools inspects
useDebugValue(user, u => `${u.name} (${u.role})`);const snapshot = useSyncExternalStore<T>(subscribe, getSnapshot, getServerSnapshot?): TSubscribes to external stores with support for concurrent rendering
const width = useSyncExternalStore(
(cb) => { window.addEventListener('resize', cb); return () => window.removeEventListener('resize', cb); },
() => window.innerWidth
);Custom Hook Patterns
function useCustomHook(): ReturnType { }Custom hooks must start with use to follow React conventions and enable lint rules
function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => JSON.parse(localStorage.getItem(key) || JSON.stringify(initial)));
return [value, setValue] as const;
}function useToggle(initial?: boolean): [boolean, () => void]Custom hook for boolean toggle state management
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue(v => !v), []);
return [value, toggle] as const;
}function usePrevious<T>(value: T): T | undefinedCustom hook to track the previous value of a prop or state
function usePrevious<T>(value: T) {
const ref = useRef<T>();
useEffect(() => { ref.current = value; }, [value]);
return ref.current;
}function useDebounce<T>(value: T, delay: number): TCustom hook to debounce a rapidly changing value
function useDebounce<T>(value: T, delay: number) {
const [debounced, setDebounced] = useState(value);
useEffect(() => { const t = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(t); }, [value, delay]);
return debounced;
}function useOnClickOutside(ref: RefObject<HTMLElement>, handler: () => void): voidCustom hook to detect clicks outside a referenced element
function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = (e) => { if (!ref.current?.contains(e.target)) handler(); };
document.addEventListener('mousedown', listener);
return () => document.removeEventListener('mousedown', listener);
}, [ref, handler]);
}function useAsync<T>(asyncFn: () => Promise<T>): { data: T | null; loading: boolean; error: Error | null }Custom hook for handling async operations with loading and error states
function useAsync<T>(fn: () => Promise<T>) {
const [state, setState] = useState({ data: null, loading: true, error: null });
useEffect(() => { fn().then(data => setState({ data, loading: false, error: null })).catch(error => setState({ data: null, loading: false, error })); }, []);
return state;
}