React Performance Antipatterns That Code Review Should Catch
React performance problems follow predictable patterns — unnecessary re-renders, expensive computations on every render, memory leaks in effects. Here's the review checklist that catches them before they ship.
React's component model is intuitive and powerful. It's also easy to write code that works correctly but performs poorly under real usage conditions — particularly conditions like large datasets, frequent state updates, and complex component trees that don't appear in development with seeded data.
Most React performance problems fall into a small number of recognizable patterns that can be caught in code review. You don't need a profiler to identify them — you need to know what to look for in the diff.
Unnecessary Re-renders from New Object References
React's re-render trigger is reference equality for objects and arrays passed as props. A component that receives an object literal as a prop — <Component config={{ timeout: 5000 }} /> — will re-render every time the parent renders, even if the timeout value never changes, because a new object is created on every render. This is the most common source of unnecessary re-renders and the one most easily caught in review.
The patterns to flag: object literals and array literals as prop values, inline arrow functions as event handlers for child components, and useEffect dependencies that include object or function references that are recreated on every render. The fixes — moving constants out of render, using useMemo for computed objects, using useCallback for event handlers — are straightforward once the pattern is identified.
Expensive Computations Without Memoization
A computation that filters a 10,000-item array on every render is fast when the array has 100 items and slow when it has 10,000. The change from "fast" to "slow" doesn't happen gradually — it happens as the dataset crosses the threshold where the computation is no longer cheap relative to the frame budget. By that point, the code has been in production for months and the performance problem is attributed to "the dataset getting too large" rather than to an optimization that was always needed.
Computations that depend on prop or state values that don't change on every render should be memoized with useMemo. The cost of useMemo is the memoization overhead (trivial) and the added code complexity (minimal). The benefit is that the computation only runs when its inputs change rather than on every parent render.
Memory Leaks in useEffect
The most common React memory leak pattern: a useEffect that sets up an event listener, a timer, or a subscription, without returning a cleanup function that removes it when the component unmounts. The effect runs on mount, the resource is allocated, the component unmounts, the resource is never released. Repeat over thousands of component mount/unmount cycles and memory usage climbs indefinitely.
In code review: every useEffect that creates a resource — addEventListener, setInterval, setTimeout, WebSocket connection, RxJS subscription — should return a cleanup function that releases it. This is documented in the React hooks documentation but missed frequently enough in practice that it should be on every reviewer's checklist.
List Rendering Without Keys
The missing key warning is one of React's most ignored warnings and one of its most important. Without stable, unique keys for list items, React falls back to position-based reconciliation — which causes incorrect behavior when list items are reordered, inserted, or removed. Using the array index as a key solves the warning but not the underlying problem: if items are reordered, React's reconciliation is still incorrect because the keys move with the positions rather than the items.
Code review for list rendering: does every list item have a key? Is the key a stable identifier from the data (ID, slug, hash) rather than the array index? For lists where items can be reordered, inserted, or removed, array index keys should be flagged explicitly.
Try CodeMouse on your next PR
Free AI code review on every pull request. Bring your own API key — no subscription needed.
Install on GitHub — Free