To improve React responsiveness, find the one interaction that feels slow, measure the components involved, remove update work that should not happen at all, and only then apply the smallest technique that fits what remains. Memoization is a targeted fix for a measured cost. It is not a default wrapper for every component, and React’s own guidance treats it that way.
Start with one slow interaction
Pick a specific interaction, such as typing in a search box, opening a panel, or switching a tab. Optimizing “the app” in general rarely produces a clear target. React’s official guidance recommends the Developer Tools Profiler when a particular interaction stays slow, because it shows which components rendered during that interaction and how long they took.
- Open the Profiler in React Developer Tools and start a recording.
- Perform the slow interaction once, then stop the recording.
- Find the commit that corresponds to the lag and list the components that rendered in it.
- Ask two questions of each one: did its output actually change, and is it expensive? Components that re-rendered with identical output and a noticeable render time are the candidates for memoization.
When you need numbers in code rather than in the extension, the Profiler component wraps a section of the tree and calls an onRender callback whenever something inside it commits an update. Two of its timing values matter most. actualDuration is the time spent rendering that update. baseDuration is an estimate of the render cost without any optimizations, which helps you judge how much memoization could plausibly save.
function onRender(id, phase, actualDuration, baseDuration) {
console.log(id, phase, actualDuration, baseDuration);
}
<Profiler id="ProductList" onRender={onRender}>
<ProductList items={items} />
</Profiler>
Keep the limits of these measurements in mind:
- Profiling adds overhead. It is disabled by default in production builds. React documents a profiling-enabled production build for situations where you need production timings.
- Development timings are not fully representative. Strict Mode can invoke render logic more than once in development, so the numbers can overstate the cost.
- Test a production build with CPU throttling. Throttling approximates the experience on slower devices, which is where responsiveness problems usually show up.
Do not report or expect a specific speedup until you have measured it in your own app, on your own build, under conditions that match your users.
#1 Best Overall
Remove update work that should not happen
Most of the time, the cheapest fix is to stop the extra renders from happening. React’s useMemo documentation puts it directly: “Most performance problems in React apps are caused by chains of updates originating from Effects that cause your components to render over and over.” An Effect that sets state, which triggers another render, which runs another Effect that sets state, produces exactly this pattern.
Three habits prevent most of it:
- Derive values during render instead of storing them in state. If a value can be computed from props or state, compute it where it is used. Copying it into state through an Effect adds a render and a chance for the copy to fall out of date.
- Keep transient state close to the components that use it. State held high in the tree re-renders everything beneath it when it changes, even when most of those children do not use it.
- Keep render logic pure. Render should read props and state and return output. Side effects belong in Effects or event handlers.
Effect dependencies deserve a separate check. If an Effect depends on an object or function created during render, that dependency changes on every render and can retrigger the Effect. Before reaching for memoization to stabilize it, move the object or function inside the Effect, or move it outside the component if it does not depend on props or state.
// Avoid: an Effect copies derived data into state
const [visible, setVisible] = useState([]);
useEffect(() => {
setVisible(items.filter(item => item.inStock));
}, [items]);
// Prefer: compute it during render
const visible = items.filter(item => item.inStock);
If the derived calculation is itself slow, the question of caching it comes next, and that is where useMemo fits.
Stop unnecessary re-renders with memo
memo wraps a component so React can skip re-rendering it when its props have not changed. By default, React compares each prop with Object.is. This works well for primitives such as strings and numbers, but it has consequences for objects, arrays, and functions:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problems- A new object, array, or function created on each parent render has a new identity, so the child’s props look changed and
memocannot skip the render. - Stabilizing those values with
useMemooruseCallbackis the usual fix, but only when the child is measured as expensive. - A custom comparison function can detect equal props that
Object.ismisses, but the comparison itself costs time, and it runs on every render of the child. memodoes not block updates caused by the component’s own state or by the context it consumes. The component still re-renders in those cases.
React’s memo documentation makes the same point in one line: “memoization is a performance optimization, not a guarantee.” Treat a successful skip as a bonus, and verify it in the Profiler rather than assuming it.
When should I use useMemo?
useMemo(() => calculate(a, b), [a, b]) stores the result of a calculation and returns the stored value on later renders as long as its dependencies remain equal under Object.is. React’s documentation states that it will not discard the cached value unless there is a specific reason to do so.
Two limits matter. First, useMemo does not make the first render faster, because nothing is cached yet. Second, the calculation must be pure, since React may rely on it producing the same result for the same inputs.
The documentation describes two situations where it earns its place: a calculation that is noticeably slow with dependencies that stay relatively stable, and a stable value that helps a memoized child skip work. The table below applies those conditions.
Recommended Free Tools
Rank #3
| Situation | Use useMemo? | Reasoning |
|---|---|---|
| A pure calculation is measured as slow, and its inputs usually stay the same | Yes | The cached result skips the calculation on renders where nothing it depends on has changed. |
An object or array is passed to a child wrapped in memo |
Yes, if the child is measured as expensive | A stable identity lets the child’s memo comparison succeed. |
| The calculation is cheap, or not shown to be a bottleneck | Usually no | There is no measured cost to remove, and the caching adds code to maintain. |
| The inputs change on almost every render | Usually no | The cache is rarely reused, so it saves little. |
| The slow part happens on the first render only | No | The documentation is explicit that useMemo does not speed up the initial render. |
If your project uses React Compiler, which can automatically memoize values, functions, and components, check its output before adding manual annotations. Duplicate manual memoization can make code harder to read without measurable benefit.
Keep typing responsive while filtering a large list
This is the case where the expensive work is necessary, but it should not block the input. A search box that re-renders a 5,000-row list on every keystroke will feel sluggish because the urgent update, the text field, waits for the expensive update, the list. React provides two tools that separate the two, and both prioritize rendering rather than making the filtering itself cheaper.
useDeferredValue for a value you receive
Use useDeferredValue when the expensive part depends on a value you read, and you do not control the code that sets it. The input stays current while the list uses a deferred copy that React can update later.
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// The input uses query; the list uses deferredQuery
<input value={query} onChange={e => setQuery(e.target.value)} />
<ProductList filter={deferredQuery} />
useTransition for an update you own
Use useTransition when you control the state update and can mark the non-urgent part as a transition. The urgent state update applies first, and the transition update follows as React has time to render it.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #4
The user-visible tradeoff is the same for both. The deferred section can temporarily show results from the previous input while the urgent input is already updated. Decide whether that brief lag is acceptable for the content, and make sure the interface does not present stale results as current.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Load rarely used code with lazy and Suspense
lazy defers loading a component’s code until it is first rendered. Wrap the import, then place the component under a Suspense boundary so React can show fallback content while the code loads.
const Chart = lazy(() => import('./Chart'));
<Suspense fallback={<p>Loading chart...</p>}>
<Chart data={data} />
</Suspense>
Placement determines the experience. A boundary high in the tree can hide a large part of the page behind one fallback, while many small boundaries can produce a series of loading indicators that flicker in sequence. Choose boundaries that match how a user moves through the screen.
The React 19 Upgrade Guide, published 25 April 2024, describes a change in how React handles a suspended component. React can commit the nearest fallback without waiting for the entire sibling tree, then schedule the suspended siblings to pre-warm their lazy requests. This is React 19 behavior, so check your version before assuming it applies to an earlier release.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Best Value
Choose the smallest technique that fits
Compare candidate techniques on what they change, which kind of bottleneck they address, and what must stay stable for them to help. The table summarizes the decision.
| Situation | Candidate pattern | What it changes | What to check |
|---|---|---|---|
| Renders come from Effects that set state | Simplify state and Effects | Removes avoidable update chains | Whether the value can be derived during render |
| A pure calculation is measured as slow and its inputs are stable | useMemo |
Reuses a calculated value on later renders | Complete, stable dependencies; no effect on the first render |
| A child is measured as expensive and its props often stay the same | memo with stable props |
Can skip some child renders | Own state and context still cause updates; new object or function props defeat the skip |
| Typing or another urgent interaction competes with expensive rendering | useTransition or useDeferredValue |
Prioritizes urgent rendering | The deferred section may briefly show older content |
| A rarely used component adds to initial code cost | lazy with Suspense |
Delays code loading and shows a fallback | Boundary placement and fallback suit the user flow |
Work down the table in order. Removing work is usually better than reprioritizing it, and reprioritizing is usually better than caching it.
When a change does not help
If the Profiler still shows the same slow commit after a change, check these causes before adding another technique:
- The measurement came from a development build. Repeat it in a production build with CPU throttling.
- The child still re-renders because it has its own state or consumes changing context.
- Props still receive a new object, array, or function on every parent render.
- A
useMemoor Effect dependency list is incomplete or changes on every render. - The slow cost is on the first render, which
useMemodoes not address. - The deferred content looks stale to users, which is the expected tradeoff of the deferral APIs and may need a clearer interface.
- A Suspense fallback appears too often or covers too much, which points to boundary placement.
Once the cause is confirmed, change one thing at a time and profile again so you can see what the change actually did.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Quick Recap
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.




