React performance optimization is about reducing unnecessary work — re-renders, computations, and bundle size. Here's a comprehensive guide.
1. Memoization — avoid unnecessary re-computation:
1import { useMemo, useCallback, memo } from "react";23// Memoize expensive computations4const sortedItems = useMemo(() => {5 return items.sort((a, b) => a.name.localeCompare(b.name));6}, [items]);78// Memoize callbacks passed to memoized children9const handleClick = useCallback((id: string) => {10 setSelected(id);11}, []);1213// Memoize components to skip re-renders14const MemoizedChild = React.memo(Child);
2. Code Splitting — load components on demand:
1import { lazy, Suspense } from "react";23const Dashboard = lazy(() => import("./Dashboard"));4const Settings = lazy(() => import("./Settings"));56function App() {7 return (8 <Suspense fallback={<div>Loading...</div>}>9 <Routes>10 <Route path="/dashboard" element={<Dashboard />} />11 <Route path="/settings" element={<Settings />} />12 </Routes>13 </Suspense>14 );15}
3. Virtualization — for long lists (1000+ items):
1import { useVirtualizer } from "@tanstack/react-virtual";2import { useRef } from "react";34function VirtualList({ items }: { items: string[] }) {5 const parentRef = useRef<HTMLDivElement>(null);67 const virtualizer = useVirtualizer({8 count: items.length,9 getScrollElement: () => parentRef.current,10 estimateSize: () => 50,11 overscan: 5,12 });1314 return (15 <div ref={parentRef} style={{ height: "500px", overflow: "auto" }}>16 <div style={{ height: `${virtualizer.getTotalSize()}px`, position: "relative" }}>17 {virtualizer.getVirtualItems().map(virtualRow => (18 <div19 key={virtualRow.index}20 style={{21 position: "absolute",22 top: 0,23 left: 0,24 width: "100%",25 height: `${virtualRow.size}px`,26 transform: `translateY(${virtualRow.start}px)`,27 }}28 >29 {items[virtualRow.index]}30 </div>31 ))}32 </div>33 </div>34 );35}
4. State colocation — keep state close to where it's used:
1// BAD: filter state at top causes ALL children to re-render2function App() {3 const [filter, setFilter] = useState("");4 return (5 <>6 <SearchInput value={filter} onChange={setFilter} />7 <ExpensiveList filter={filter} />8 </>9 );10}1112// GOOD: move state to where it matters13function SearchableList() {14 const [filter, setFilter] = useState("");15 return (16 <>17 <SearchInput value={filter} onChange={setFilter} />18 <ExpensiveList filter={filter} />19 </>20 );21}
5. React DevTools Profiler — find the bottleneck:
6. Production build — always use production mode:
NODE_ENV=production7. Avoid unnecessary object/array creation in render:
1// BAD: new object every render = child always re-renders2<Child style={{ color: "red" }} />34// GOOD: constant object5const STYLE = { color: "red" };6<Child style={STYLE} />78// GOOD: or memoize it9const style = useMemo(() => ({ color: active ? "blue" : "red" }), [active]);10<Child style={style} />