React 18 Hooks Best Practices and Gotchas

useState

Managing state requires understanding how React batches updates and handles asynchronous behavior.

  • Functional Updates: To retrieve the latest state immediately within an update, use the functional update pattern: ``` setCounter(prev => { console.log(prev); // Always reflects the current state return prev + 1; });
  • Local Variables: If a variable does not impact the UI (render), do not use useState. Use standard variables or useRef to avoid unnecessary re-renders.
  • Lazy Initialization: If the initial state calculation is expensive, pass a function to useState to ensure it only executes during the component's initial mount.
  • State Immutability: React compares states by reference. When updating objects or arrays, always pass a new reference (e.g., using the spread operator or libraries like Immer). Directly modifying the state object will not trigger a re-render.

useRef

  • DOM Access in Lists: Use a callback ref patern to manage an array of DOM elements dynamically: ``` const refs = useRef([]); // In JSX: <div ref={el => refs.current[i] = el} />
  • Instance Isolation: Always declare useRef inside the component body to ensure each instance of the component has its own isolated storage. Never declare refs in the module scope.
  • Non-Reactive Storage: useRef is perfect for storing data that persists across renders without triggering them.

useEffect

  • Handling Closures: A common pitfall is accessing stale state inside an effect. ``` // Instead of relying on closure, use the functional update useEffect(() => { const timer = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(timer); }, []);
  • Strict Dependency Management: React 18's Strict Mode executes effects twice in development to identify potential memory leaks. Ensure your cleanup functions properly tear down observers or timers.
  • Async Functions: useEffect cannot return a promise. Wrap asynchronous logic in an internal function: ``` useEffect(() => { const fetchData = async () => { const data = await fetch(url).then(res => res.json()); setData(data); }; fetchData(); }, []);
  • Avoiding Cycles: If an effect updates state that it also depends on, use the functional setter pattern to avoid listing the state in the dependency array.

Tags: React react-hooks frontend-development javascript state-management

Posted on Sun, 27 Sep 2026 16:18:37 +0000 by tina88