Performance issues in React apps are frustrating — and tricky. You make one small change and suddenly the page lags, clicks delay, or scrolling becomes janky. We recently ran into this exact situation, and I want to share not just what we did, but how we approached it — because the approach matters just as much as the fix.

Here’s how we tackled it.


Step 1: Recognize the Problem Early

Performance issues rarely hit all at once — they creep in as your app scales. That’s why it’s always worth asking early:

“It’s fast now… but what happens when this list grows to 100 or 200 items?”

In our case, we had a page displaying editable form items. Everything worked great — at first. But once the number of items grew, cracks started to show:

  • Around 100 items: it started to lag
  • At 500: noticeable slowdowns
  • By 1000: it was borderline unusable

  • Initial render was painfully slow

  • Scrolling lagged

  • User interactions were sluggish

One common solution here is pagination — but that didn’t work for us.

Why? Because our users needed to see and interact with the entire list at once. Breaking it up would have killed their workflow.

So we knew we had to fix the performance itself, not just hide it behind paging.


Step 2: Define What "Good" Looks Like

Before jumping into optimization, take a step back:

What does “solved” actually mean?

Performance is a rabbit hole — you could spend days tuning things that don't actually help users. So we aligned on two things:

  • Metrics that matter:

    We used Web Vitals to set our success criteria.

    • LCP (Largest Contentful Paint) under 2.5s
    • INP (Interaction to Next Paint) under 200ms
  • Realistic usage scope:


    We checked with stakeholders: would users really have 1000 items?


    Answer: not likely. The real-world upper limit was closer to 200.


    That helped us stay focused and avoid over-engineering.

✅ With clear metrics (LCP, INP) and realistic scope (200 items), we had a solid definition of “problem solved.”


Step 3: Investigate the Root Cause

Once we had our target and scope locked down, the next step was to figure out what's actually causing the slowness.

Frontend performance issues can come from many places:

  • Too many DOM nodes?
  • Inefficient state updates?
  • Large bundle size?
  • Layout shifts?
  • Expensive third-party components?
  • Or… unnecessary React re-renders?

We looked at all of those. But in React apps, unnecessary re-rendering is one of the most common — and often easiest — performance killers.

And thanks to tools like React Scan, it’s surprisingly quick to check.

.

Sure enough, React Scan showed a lot of wasteful re-renders — even small updates were triggering large parts of the UI to update. That gave us a clear direction for optimization.

Optimization: Minimizing Re-renders

Once we pinpointed where unnecessary re-renders were happening, we took action by applying a few common React techniques:

  • useCallback: This hook ensures that functions are not redefined unnecessarily, preventing components from re-rendering when their props haven’t changed. We used it to memoize event handlers and callbacks that were being redefined on every render.

  • useMemo and React.memo: Both of these were used to prevent unnecessary recalculations. We memoized expensive computations and wrapped functional components with React.memo to avoid unnecessary re-renders when props didn’t change.

These optimizations reduced the number of renders, kept the UI smoother, and ultimately helped us stay on top of performance.


Even after implementing these improvements, the application still felt rough when dealing with 100-200 items. While the app was smoother and we saw better metric scores, it wasn't quite there yet.

So what did we do next?

I’ll share our next steps — and how we introduced virtual scrolling — in the next post.