7 React Mistakes That Slow Down Your App (With Fixes & Examples)

7 React Mistakes That Slow Down Your App (With Fixes & Examples)

7 React Mistakes That Slow Down Your App (With Fixes & Examples)

A story-driven, example-rich guide to React performance. See the exact mistakes (with ❌ wrong vs ✅ right code) that silently make apps feel laggy—and how to fix them.

By ·

When I first started building with React, I assumed that just by using a modern library my UI would be fast. A few months and a couple of frustrated users later, I realized something important: React is fast, but only if we write with performance in mind.

This post walks through seven common mistakes I’ve made (and seen in real projects), each with a short story, a wrong example, and a right fix you can copy-paste.

1) Re-rendering Everything on State Change

Story: I once held most of my app’s state at the top and passed it down as props. A tiny counter update made the entire page re-render—and animations stuttered.

// ❌ Wrong: all children re-render on every count change
function App() {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <Header count={count} />
      <Content count={count} />
      <Footer count={count} />
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}
// ✅ Better: localize state + memoize dependents
const Header = React.memo(({ count }) => <h1>{count}</h1>);

function App() {
  const [count, setCount] = React.useState(0);
  return (
    <>
      <Header count={count} />
      <Content /> {/* unaffected by count */}
      <Footer />
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </>
  );
}
Takeaway: Split state by concern and wrap stable components with React.memo so unrelated parts don’t rerender.

2) Inline Functions Everywhere

Story: A product list with 200 buttons created 200 fresh function instances per render. On older devices, clicks felt sticky.

// ❌ New function each render per item
{items.map((item) => (
  <button key={item.id} onClick={() => handleClick(item)}>Buy</button>
))}
// ✅ Stabilize with useCallback
function Items({ items }) {
  const handleClick = React.useCallback((item) => {
    // logic
  }, []);
  return items.map((item) => (
    <button key={item.id} onClick={() => handleClick(item)}>Buy</button>
  ));
}
Tip: Memoization only helps if children also accept stable props (memoized or primitives).

3) Missing or Unstable key Props in Lists

Story: I used indexes as keys; after a sort, React couldn’t match items properly and re-rendered the whole list.

// ❌ Using index breaks identity on reorder
{items.map((item, i) => <li key={i}>{item.name}</li>)}
// ✅ Use stable unique IDs
{items.map((item) => <li key={item.id}>{item.name}</li>)}
Takeaway: Keys are identity. Wrong keys force extra DOM work and wasted renders.

4) Overusing Context API

Story: I stuffed theme, user, settings, and feature flags into a single Context. Updating the theme repainted half the app.

// ❌ Single context for everything = large render surface
<AppContext.Provider value={{ theme, user, settings, flags }}>
  <App />
</AppContext.Provider>
// ✅ Split contexts by change frequency & consumption
<ThemeContext.Provider value={theme}>
  <UserContext.Provider value={user}>
    <SettingsContext.Provider value={settings}>
      <App />
    </SettingsContext.Provider>
  </UserContext.Provider>
</ThemeContext.Provider>
Pro move: For complex state, use Redux Toolkit or Zustand. Context shines for static or rarely-changing values.

5) Loading Too Much Data at Once

Story: I loaded 10,000 rows on page load because the API allowed it. On mid-range phones, the UI froze for seconds.

// ❌ Fetch everything, block UI
React.useEffect(() => {
  fetch("/api/users").then(r => r.json()).then(setUsers);
}, []);
// ✅ Paginate or infinite scroll
const [page, setPage] = React.useState(1);
React.useEffect(() => {
  fetch(`/api/users?page=${page}&limit=20`)
    .then(r => r.json())
    .then((data) => setUsers(prev => [...prev, ...data]));
}, [page]);
Also consider: virtualized lists (e.g., react-window) so you only render what’s visible.

6) Not Using React DevTools Profiler

Story: I spent days guessing which components were slow. The Profiler revealed a navbar re-rendering on every click due to an unstable prop.

How to profile quickly: Open React DevTools → Profiler → Start profiling → Perform your action → Stop → Sort by “Commit duration” → Inspect components with frequent or expensive renders.
// ✅ Quick sanity check in code
console.time("render");
root.render(<App />);
console.timeEnd("render");
Tip: Fix the biggest offenders first (long tasks > 50ms). Users feel those the most.

7) Ignoring Image Optimization

Story: A beautiful 4MB hero image undid all my memoization wins. First paint was painfully slow.

// ❌ Heavy, render-blocking images
<img src="/img/hero.png" alt="Hero" />
// ✅ Compress + modern formats + lazy
<img src="/img/hero.webp" alt="Hero" loading="lazy" width="1200" height="630" />
Checklist: Use WebP/AVIF; pre-size images; lazy-load non-critical; serve via CDN; consider responsive srcset.

Performance Checklist (Copy & Pin)

  • Localize state; avoid cascading re-renders.
  • Stabilize handlers with useCallback; memoize children.
  • Use stable unique keys for lists.
  • Split Contexts or use a state library for large, changing state.
  • Paginate / infinite-scroll; virtualize large lists.
  • Profile with React DevTools; fix the biggest offenders first.
  • Compress & lazy-load images; prefer WebP/AVIF; use a CDN.

FAQ

How do I know where to start optimizing?

Profile real interactions in React DevTools and Chrome Performance. Focus on long tasks, frequent re-renders, and heavy images first.

Is memoization always good?

No. Memoization adds overhead. Use it where props are stable and re-renders are costly—especially in large lists or complex components.

Do I need a state library?

For small apps, Context + local state works. As your app grows, libraries like Redux Toolkit or Zustand can reduce unnecessary renders and improve structure.

Final Thoughts 💡

React isn’t slow—our patterns can be. If you avoid these seven mistakes, your app will feel immediately snappier on real devices. Even one improvement (like proper keys or image optimization) can make a visible difference.

Your turn: Which mistake bit you recently? Share in the comments—I read every one. If this helped, consider sharing it with a teammate.

Follow me on Medium

Comments

Popular posts from this blog

Docker for Beginners: Build, Ship, and Run Apps with Ease

Top 7 Real-World Projects to Learn React, Python, and AWS (Beginner to Advanced)