📖 Guide
React Hooks Reference
Complete React Hooks reference — useState, useEffect, useContext, custom hooks, and common patterns.
81 commands across 12 categories
useStateuseEffectuseContextuseReduceruseCallbackuseMemouseRefuseLayoutEffectuseIdCustom Hooks PatternsRules of HooksCommon Patterns
useState
| Command | Description |
|---|---|
const [count, setCount] = useState(0) | Initialize state with a value |
const [name, setName] = useState("") | Initialize state with a string |
const [user, setUser] = useState<User | null>(null) | Initialize state with explicit type (TypeScript) |
const [items, setItems] = useState<string[]>([]) | Initialize state with typed empty array |
setCount(5) | Set state to a specific value |
setCount(prev => prev + 1) | Update state based on previous value (functional update) |
setItems(prev => [...prev, newItem]) | Add item to array state |
setItems(prev => prev.filter(i => i.id !== id)) | Remove item from array state |
setUser(prev => ({ ...prev, name: "new" })) | Update a property in object state |
const [state, setState] = useState(() => expensiveComputation()) | Lazy initialization — function runs only on first render |
useEffect
| Command | Description |
|---|---|
useEffect(() => { /* runs after every render */ }) | Effect with no dependency array — runs after every render |
useEffect(() => { /* runs once */ }, []) | Effect with empty deps — runs only on mount |
useEffect(() => { /* runs when count changes */ }, [count]) | Effect with dependencies — runs when specified values change |
useEffect(() => { return () => { /* cleanup */ } }, []) | Cleanup function — runs on unmount or before re-running effect |
useEffect(() => {
const sub = api.subscribe(handler);
return () => sub.unsubscribe();
}, []) | Subscribe/unsubscribe pattern |
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal });
return () => controller.abort();
}, [url]) | Fetch with abort cleanup — prevent state updates on unmounted component |
useEffect(() => {
const timer = setInterval(tick, 1000);
return () => clearInterval(timer);
}, []) | Timer with cleanup |
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]) | Sync document title with state |
useEffect(() => {
const handler = (e: KeyboardEvent) => { /* ... */ };
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, []) | Add/remove event listener |
useContext
| Command | Description |
|---|---|
const ThemeContext = createContext<"light" | "dark">("light") | Create a context with a default value |
const UserContext = createContext<User | null>(null) | Create a context with nullable default |
<ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider> | Provide context value to children |
const theme = useContext(ThemeContext) | Consume context value in a component |
function useTheme() { const ctx = useContext(ThemeContext); if (!ctx) throw new Error("Must be in ThemeProvider"); return ctx } | Custom hook with context validation |
const AuthContext = createContext<{ user: User; login: () => void } | undefined>(undefined) | Context with actions and state |
function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
return <AuthContext.Provider value={{ user, login }}>{children}</AuthContext.Provider>;
} | Provider component pattern with state |
useReducer
| Command | Description |
|---|---|
const [state, dispatch] = useReducer(reducer, initialState) | Initialize useReducer with a reducer function and initial state |
type Action = { type: "increment" } | { type: "decrement" } | { type: "set"; payload: number } | Define action types with discriminated union |
function reducer(state: State, action: Action): State {
switch (action.type) {
case "increment": return { count: state.count + 1 };
case "decrement": return { count: state.count - 1 };
default: return state;
}
} | Reducer function with switch statement |
dispatch({ type: "increment" }) | Dispatch an action |
dispatch({ type: "set", payload: 42 }) | Dispatch an action with payload |
const [state, dispatch] = useReducer(reducer, arg, init) | Lazy initialization — init function transforms arg into initial state |
const [todos, dispatch] = useReducer(todosReducer, []) | Manage a list with useReducer |
useCallback
| Command | Description |
|---|---|
const handleClick = useCallback(() => { setCount(c => c + 1) }, []) | Memoize a callback with no dependencies |
const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { setName(e.target.value) }, []) | Memoize an event handler |
const fetchData = useCallback(async () => { const res = await fetch(url); setData(await res.json()) }, [url]) | Memoize async function — re-creates when url changes |
const handleSubmit = useCallback((data: FormData) => { api.submit(id, data) }, [id]) | Memoize callback that depends on a prop |
<ChildComponent onClick={handleClick} /> | Pass memoized callback to prevent child re-renders |
const fn = useCallback(handler, deps)
// equivalent to:
const fn = useMemo(() => handler, deps) | useCallback is shorthand for useMemo returning a function |
useMemo
| Command | Description |
|---|---|
const sorted = useMemo(() => items.sort((a, b) => a - b), [items]) | Memoize expensive computation |
const filtered = useMemo(() => items.filter(i => i.includes(query)), [items, query]) | Memoize filtered list — recalculates when items or query change |
const chartData = useMemo(() => processData(rawData), [rawData]) | Memoize data transformation |
const style = useMemo(() => ({ color: dark ? "white" : "black" }), [dark]) | Memoize object to maintain referential equality |
const Component = useMemo(() => <ExpensiveTree data={data} />, [data]) | Memoize JSX element |
const value = useMemo(() => ({ state, dispatch }), [state]) | Memoize context value to prevent unnecessary re-renders |
useRef
| Command | Description |
|---|---|
const inputRef = useRef<HTMLInputElement>(null) | Create a ref for a DOM element |
<input ref={inputRef} /> | Attach ref to a DOM element |
inputRef.current?.focus() | Access DOM element via ref |
const renderCount = useRef(0) | Create a mutable ref that persists across renders |
renderCount.current += 1 | Mutate ref value (does not trigger re-render) |
const prevValue = useRef(value)
useEffect(() => { prevValue.current = value }, [value]) | Track previous value pattern |
const callbackRef = useRef(callback)
useEffect(() => { callbackRef.current = callback }) | Store latest callback in ref (avoid stale closures) |
const timerRef = useRef<ReturnType<typeof setInterval>>(null) | Store timer/interval ID in ref |
useLayoutEffect
| Command | Description |
|---|---|
useLayoutEffect(() => {
const { height } = ref.current!.getBoundingClientRect();
setHeight(height);
}, [dep]) | Measure DOM before browser paint — prevents visual flicker |
useLayoutEffect(() => {
ref.current!.style.transform = `translateX(${x}px)`;
}, [x]) | Synchronously update DOM position |
useLayoutEffect(() => {
const el = ref.current!;
el.scrollTop = el.scrollHeight;
}, [messages]) | Scroll to bottom before paint |
// useLayoutEffect fires synchronously after DOM mutations
// but before the browser paints
// useEffect fires after paint | Timing: useLayoutEffect vs useEffect |
useId
| Command | Description |
|---|---|
const id = useId() | Generate a unique ID stable across server/client rendering |
<label htmlFor={id}>Name</label>
<input id={id} /> | Use generated ID for form label-input association |
const emailId = useId()
const passwordId = useId() | Generate multiple IDs in a component |
<div aria-labelledby={id}> | Use ID for ARIA attributes |
Custom Hooks Patterns
| Command | Description |
|---|---|
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue(v => !v), []);
return [value, toggle] as const;
} | useToggle — boolean toggle hook |
function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initial;
});
useEffect(() => { localStorage.setItem(key, JSON.stringify(value)) }, [key, value]);
return [value, setValue] as const;
} | useLocalStorage — persist state to localStorage |
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;
} | useDebounce — debounce a rapidly changing value |
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(r => r.json()).then(setData).catch(setError).finally(() => setLoading(false));
return () => controller.abort();
}, [url]);
return { data, loading, error };
} | useFetch — data fetching hook with loading/error states |
function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(() => window.matchMedia(query).matches);
useEffect(() => {
const mql = window.matchMedia(query);
const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
mql.addEventListener("change", handler);
return () => mql.removeEventListener("change", handler);
}, [query]);
return matches;
} | useMediaQuery — responsive design hook |
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => { ref.current = value });
return ref.current;
} | usePrevious — track the previous value of a prop/state |
function useOnClickOutside(ref: RefObject<HTMLElement>, handler: () => void) {
useEffect(() => {
const listener = (e: MouseEvent) => {
if (!ref.current?.contains(e.target as Node)) handler();
};
document.addEventListener("mousedown", listener);
return () => document.removeEventListener("mousedown", listener);
}, [ref, handler]);
} | useOnClickOutside — detect clicks outside an element |
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef(callback);
useEffect(() => { savedCallback.current = callback });
useEffect(() => {
if (delay === null) return;
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}, [delay]);
} | useInterval — declarative setInterval hook |
Rules of Hooks
| Command | Description |
|---|---|
// ✅ Call hooks at the top level of your component | Rule 1: Only call hooks at the top level — not inside loops, conditions, or nested functions |
// ❌ if (condition) { useState(0) } | Never call hooks conditionally — React relies on call order being consistent |
// ✅ Call hooks from React function components or custom hooks | Rule 2: Only call hooks from React functions — not regular JavaScript functions |
// ✅ function useCustom() { useState(0); useEffect(() => {}) } | Custom hooks can call other hooks |
// ✅ Name custom hooks starting with "use" | Convention: custom hooks must start with 'use' for linter to enforce rules |
// ❌ for (const item of items) { useEffect(() => {}) } | Never call hooks in loops — call count must be identical every render |
Common Patterns
| Command | Description |
|---|---|
const [state, setState] = useState(initialState)
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setState(prev => ({ ...prev, [e.target.name]: e.target.value }))
} | Dynamic form state with computed property names |
useEffect(() => {
let cancelled = false;
async function load() {
const data = await fetchData();
if (!cancelled) setData(data);
}
load();
return () => { cancelled = true };
}, [dep]) | Avoid setting state after unmount (race condition) |
const [, forceUpdate] = useReducer(x => x + 1, 0) | Force re-render trick (escape hatch) |
function useStableCallback<T extends (...args: any[]) => any>(fn: T): T {
const ref = useRef(fn);
ref.current = fn;
return useCallback((...args: any[]) => ref.current(...args), []) as T;
} | Stable callback ref — always references latest function without re-creating |
const isMounted = useRef(true);
useEffect(() => { return () => { isMounted.current = false } }, []) | Track mount status with ref |
function useRenderCount() {
const count = useRef(0);
count.current++;
return count.current;
} | Debug: count component renders |
📖 Free, searchable command reference. Bookmark this page for quick access.