Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

After enabling React Compiler in our React 19 dashboard, we removed about 60% of its manual useMemo and useCallback calls across three small pull requests. The biggest gains came from screens that had not been carefully memoized; already optimized areas showed no measurable change. We kept roughly 80 calls where compiler coverage or reference identity still mattered.

What React Compiler changed in our dashboard

Our product dashboard combined a large virtualized data table, several charts, and a draggable timeline scrubber. Over time, defensive optimization and code-review habits had left us with 214 useMemo and useCallback calls. We treated many as routine protection against unnecessary renders, even when they made the code harder to follow.

We enabled React Compiler gradually, starting in annotation mode and later switching to default mode. For components we were not ready to trust, we used the use no memo directive. Before removing manual memoization, we ran the compiler healthcheck and enabled compiler-aware lint rules from eslint-plugin-react-hooks. The lint output became a useful migration backlog: it showed which components could not yet be compiled and why.

Across three feature-scoped pull requests, we removed about 60% of the manual memoization: from 214 calls to roughly 80. That is our observation from one application, not a React Compiler benchmark or a guarantee for other apps.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What we removed—and what got simpler

In a filtered and sorted list, the compiler let us delete manual memoization and its dependency arrays. The resulting code had fewer opportunities for stale-closure mistakes and less wrapper code to maintain. We also found it could handle a summary calculation placed after a possible early return—an awkward shape to memoize manually.

We did not remove calls just because the compiler was enabled. Each of the roughly 80 calls we kept had a documented reason, such as a boundary where reference identity mattered or a component that was not compiled.

What changed in performance

For each pull request, we replayed the same scripted interaction before and after, then compared commit counts and render durations in React DevTools Profiler. We also compared real-user Interaction to Next Paint (INP) over one week before and one week after, and checked bundle size because compiler output adds code.

Area or measure What we observed
Areas already well memoized Commit counts and render durations stayed within noise.
Settings panel and several screens that re-rendered entire subtrees on every keystroke The clearest improvement; real-user INP improved noticeably on mid-range Android devices.
Main bundle It grew slightly—by a couple of percent, in our application.
Overall result “No speedup where we’d been careful, a real one where we hadn’t, and a lot less code overall.”

Those results come from our dashboard and measurement windows; they should not be read as a general performance promise. The practical pattern was straightforward: careful existing memoization left little room for the compiler to improve, while unoptimized render paths offered more opportunity.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Where compiler coverage and reference identity still mattered

A component that mutated its input

In staging, the compiler silently skipped a component that sorted a scores prop in place. We fixed the mutation by copying the array before sorting. The lesson was not to assume every component is compiled: check the compiler and lint feedback, and fix code that prevents compilation.

A legacy chart wrapper with a fresh options object

A legacy chart wrapper passed a newly created options object to a compiled child on every render. The prop’s identity changed before the compiled code could help, so we restored manual useMemo at that boundary. Compiler optimization does not erase identity changes introduced by uncompiled parents, and third-party components may rely on reference equality.

Why the timeline scrubber still stuttered

The scrubber kept stuttering because pointer movement drove more than 60 state updates per second. Memoization could reduce avoidable work, but it could not make continuous React updates free. We moved drag updates to a CSS variable updated with requestAnimationFrame, then told React only when the drag ended. That reduced the work React had to process during the gesture.

When pointer, scroll, or drag input arrives faster than the UI needs React state updates, consider browser-level mechanisms such as refs, CSS variables, and requestAnimationFrame rather than sending every event through React.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

A migration sequence that kept changes reviewable

  1. Enable React Compiler and run its healthcheck before deleting existing memoization.
  2. Enable compiler-aware lint rules in eslint-plugin-react-hooks; fix violations and use the remaining output to identify components that cannot be compiled.
  3. Roll out in annotation mode, or opt out components you are not ready to trust with use no memo.
  4. Remove manual memoization in small, feature-scoped pull requests instead of making one broad rewrite.
  5. Profile the same interactions before and after each change so regressions are easier to isolate.
  6. Keep manual memoization at uncompiled boundaries and anywhere a consumer requires stable reference identity.
  7. For high-frequency input, reduce React’s workload with refs, CSS variables, or requestAnimationFrame instead of relying on memoization alone.

When to remove a memo—and when to keep it

Situation Practical choice
The compiler handles the component, and profiling shows no boundary requiring stable identity Try removing the manual memoization; simpler code may be the main benefit.
A parent that is not compiled creates a new object or function passed to a compiled child Consider retaining memoization at that boundary if identity changes cause work or break a consumer’s expectations.
A third-party component depends on reference equality Preserve stable references where the integration requires them.
A component is skipped because of a compiler limitation or code pattern Use the healthcheck and lint feedback to address the cause, or keep its manual optimization where needed.
Pointer, scroll, or drag updates arrive at high frequency Address the event path with browser primitives; memoization is not a substitute for reducing update frequency.

React Compiler gave us fewer manual caches, simpler code, and a performance improvement in parts of the dashboard that needed it. It did not improve every screen, remove the need to inspect compiler coverage, or solve a workload driven by continuous input.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.