Skip to Content
FrontendReactIntermediate2.1 React Hooks (Core)

Hooks (Core) 🧠

Hooks let function components manage state, side effects, performance, and shared logic. Use them thoughtfully to keep UI predictable.

1) useEffect Deep Dive 🔄

Correct Patterns ✅

  • Keep effects focused on a single concern (fetching, subscriptions, timers).
  • Always list every reactive value you use in the dependency array.
  • Guard asynchronous work with cleanup to avoid stale state.
useEffect(() => { let cancelled = false; async function loadProfile() { const response = await fetch(`/api/users/${userId}`); if (!cancelled) setProfile(await response.json()); } loadProfile(); return () => { cancelled = true; }; }, [userId]);

Pitfalls ❌

  • Fetching inside render instead of effects causes infinite loops.
  • Omitting dependencies hides bugs; rely on ESLint to warn you.
  • Doing heavy work in effects without throttling can freeze the UI.

2) useRef for DOM and Mutable Values 🧱

  • useRef stores a .current property that persists across renders without causing re-renders.
  • Use for DOM nodes, timers, or memoized mutable values.
import { useEffect, useRef } from "react"; function FocusInput() { const inputRef = useRef(null); useEffect(() => { inputRef.current?.focus(); }, []); return <input ref={inputRef} placeholder="Auto focus" />; }
  • Store previous values: const prevCount = useRef(count); useEffect(() => { prevCount.current = count; });

3) useMemo & useCallback ⚙️

  • useMemo(fn, deps) caches a value; useCallback(fn, deps) caches a function.
  • Use them to avoid expensive recalculations or unnecessary prop changes only when profiling reveals a bottleneck.
const filteredTodos = useMemo(() => { return todos.filter(todo => todo.done === showDone); }, [todos, showDone]); const handleSelect = useCallback(id => { onSelect(id); }, [onSelect]);

🧠 Rule: optimize when a component re-renders too often or when child components rely on referential equality.

4) useReducer for Complex State 🧠

  • Prefer when state transitions follow clear actions.
  • Keeps updates predictable and colocated with logic.
const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case "increment": return { count: state.count + 1 }; case "reset": return initialState; default: throw new Error("Unknown action"); } } const [state, dispatch] = useReducer(reducer, initialState);
  • Dispatch actions from UI: dispatch({ type: "increment" });

5) Custom Hooks 🔁

  • Extract reusable logic into functions starting with use... (enforce hook rules).
  • Share logic without HOCs or render props.
import { useEffect, useState } from "react"; function useOnlineStatus() { const [online, setOnline] = useState(navigator.onLine); useEffect(() => { function update() { setOnline(navigator.onLine); } window.addEventListener("online", update); window.addEventListener("offline", update); return () => { window.removeEventListener("online", update); window.removeEventListener("offline", update); }; }, []); return online; } function StatusBadge() { const online = useOnlineStatus(); return <span>{online ? "🟢 Online" : "🔴 Offline"}</span>; }

Key Takeaways ✅

  • useEffect should be deterministic: list dependencies, clean up side effects.
  • useRef stores data that does not trigger renders (DOM nodes, previous values).
  • useMemo/useCallback are performance tools; use sparingly and measure impact.
  • useReducer organizes complex state transitions; custom hooks package logic for reuse.

Recap 🔄

Hooks are composable primitives: useEffect handles side effects, useRef preserves mutable values, memo hooks optimize re-renders, useReducer structures complex state, and custom hooks let you share behavior cleanly. Use them intentionally to keep components tidy and predictable.

Hook Reference 🗂️

  • useState — store local component state.
  • useEffect — run side effects after render.
  • useContext — read values from the nearest matching Provider.
  • useReducer — manage complex state transitions with actions.
  • useCallback — memoize function references between renders.
  • useMemo — cache expensive computations.
  • useRef — hold mutable values or DOM nodes without re-rendering.
  • useId — generate stable IDs for accessibility attributes.
  • useLayoutEffect — run synchronous effects after DOM mutations (before paint).
  • useInsertionEffect — inject critical styles before layout calculations.
  • useImperativeHandle — customize refs exposed by components.
  • useDebugValue — label custom hooks in React DevTools.
  • useDeferredValue — postpone rendering a value to keep UI responsive.
  • useTransition — mark state updates as non-urgent for smoother UX.
  • useSyncExternalStore — subscribe to external stores with concurrent safety.
  • useOptimistic — show optimistic UI values while awaiting confirmation.
Last updated on