10 React Performance Mistakes That Make Your App Feel Slow
React is fast by default — until it isn't. Most performance issues aren't React's fault — they're patterns that seemed harmless at first but compound into real problems as your app scales. Here are the 10 most common mistakes and exactly how to fix them.
1. Creating new objects/arrays in render. Every time you write `style={{color: 'red'}}` or `options={[1,2,3]}` inline, React sees a new reference and re-renders the child. Fix: move constants outside the component, or wrap in useMemo. This alone can eliminate 30–50% of unnecessary renders in a typical app.
2. Not using React.memo on expensive components. If a component receives the same props but re-renders because its parent re-rendered — wrap it in React.memo. But don't memo everything blindly — the comparison has a cost. Use it on components that: render often, receive the same props frequently, and are expensive to render (large lists, charts, complex UIs).
3. Missing keys or using index as key. Using array index as key in lists causes React to unmount and remount elements when the list changes, instead of moving them. Always use a stable, unique ID. If your data doesn't have IDs, generate them once on data fetch — not in render.
4. Uncontrolled re-renders from context. A single Context that holds your entire app state re-renders every consumer on any state change. Fix: split context by domain (AuthContext, ThemeContext, CartContext). Or use Zustand/Jotai which have built-in selector-based subscriptions that only re-render when your selected slice changes.
5. Fetching data inside useEffect without cleanup. Race conditions, memory leaks, and waterfall requests. Fix: use React Query (TanStack Query) or SWR. They handle caching, deduplication, background refetching, and error retry automatically. If you're still writing `useEffect + fetch + useState` in 2025, you're working too hard.
6. Not code-splitting routes. Shipping your entire app as one bundle means users download the settings page JavaScript while looking at the homepage. Use React.lazy + Suspense for route-level code splitting. In Next.js, this happens automatically with the App Router — every page is a separate chunk.
7. Oversized bundle from barrel exports. `import { Button } from '@/components'` pulls in every component if your index.ts re-exports everything. Tree-shaking helps but isn't perfect. Use direct imports: `import { Button } from '@/components/Button'`.
8. Not virtualizing long lists. Rendering 1000+ items in a list is a guaranteed performance kill. Use react-window or TanStack Virtual to only render visible items. We've seen this single change reduce Time to Interactive from 8 seconds to under 1 second on list-heavy pages.
9. Expensive computations on every render. Sorting, filtering, or transforming large datasets on every render burns CPU. Wrap in useMemo with proper dependency arrays. For really expensive operations, move them to a Web Worker so they don't block the main thread.
10. Not measuring before optimizing. Don't guess — use React DevTools Profiler to identify which components are re-rendering and why. Use Chrome DevTools Performance tab to find long tasks. Use why-did-you-render library to catch unnecessary re-renders in development. Optimizing the wrong thing is worse than not optimizing at all.
Building AI-heavy SaaS products, running a digital agency, and sharing everything I learn along the way.
Ready to build something extraordinary?
Book a free 30-minute strategy call. No pitch decks, no fluff — just a clear plan for your project.