Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Most slow React screens are not fixed by wrapping components in memo. The reliable order is to find the interaction that feels slow, measure which part of the tree does the work, remove updates that should not happen at all, and only then apply the smallest technique that fits what remains. This guide follows that order and explains what React’s official documentation says about each tool, including where each one stops helping.
Start with one interaction that feels slow
Pick a specific user action: typing in a search box, switching a tab, or opening a filter panel. Write down what should happen and what actually happens. Optimizing “the whole app” usually leads to memoizing components that were never part of the problem. A single interaction gives you a measurable target and a way to tell whether a change helped.
Check whether React Compiler already handles memoization
React Compiler can automatically memoize values, functions, and components. In a project where it is enabled in the build setup, many of the manual techniques below may be unnecessary, and adding them by habit can make code harder to read without a clear gain. Confirm the compiler’s status in your own build configuration before deciding that manual memoization is needed. If it is not enabled, the patterns in the rest of this article apply directly.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Measure the component tree before changing it
React’s Profiler component wraps a section of the tree and calls an onRender callback whenever a component inside that section commits an update. Use it to find out which subtree is expensive, not to guess.
#1 Best Overall
- Open your app in development, then open the browser’s React Developer Tools and select the Profiler tab.
- Start a recording, perform the slow interaction once, and stop the recording.
- Inspect the commits that took the longest. Identify the component that is re-rendering and the parents that caused it.
- If the ranking is not specific enough, wrap the suspected subtree in a
Profilerto get timing for that section alone:
import { Profiler } from 'react';
function onRender(id, phase, actualDuration, baseDuration) {
console.log(id, phase, { actualDuration, baseDuration });
}
<Profiler id="ResultsList" onRender={onRender}>
<ResultsList results={results} />
</Profiler>
The two timing fields answer different questions:
| Field | What it measures | How to read it |
|---|---|---|
actualDuration |
Time spent rendering the update that was just committed | High values mean that update is costly as it stands |
baseDuration |
Estimated render time for the subtree without memoization | A large gap between this and actualDuration suggests existing memoization is already helping; similar values suggest the subtree renders fully each time |
id and phase |
Which profiled section committed, and whether it was a mount or an update | Separates first render cost from repeated update cost |
Profile something closer to production
Profiling adds overhead, and development builds behave differently from production builds. React’s documentation for useMemo notes that development measurements are not always representative: Strict Mode can invoke render logic more than once in development. Test a production build, and use CPU throttling in your browser’s performance tools to approximate slower devices. The Profiler is disabled by default in production builds; React documents a separate profiling-enabled production build for cases where you need production timings. Use that build only when you need it, and compare results from the same environment before and after each change.
Remove update work that should not happen
Before adding any memoization, check whether the component is rendering because of avoidable updates. The most common cause is a chain of state updates driven by Effects. 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.”
Keep transient state close to where it is used
State that only one small component needs should live in that component. When state sits high in the tree, every change re-renders everything beneath it, including components that do not read the value. Moving the state down is often a larger improvement than any memoization step, and it costs no added code.
Derive values during render instead of syncing them with Effects
A value that can be computed from props or state does not need its own state variable and Effect. The Effect version adds an extra render pass; the derived version does not.
// Avoid: an Effect copies derived data into state, causing an extra render
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// Prefer: compute it during render
const fullName = firstName + ' ' + lastName;
Simplify Effect dependencies before reaching for memoization
When an object or function appears in an Effect’s dependency list and changes every render, the first fix is to move it inside the Effect or outside the component if it does not depend on props or state. Wrapping it in useMemo or useCallback just to keep the dependency stable adds a second mechanism that must stay correct. Do that only when moving the code is not possible.
Use useMemo for a calculation that is measurably slow
useMemo caches the result of a calculation between renders. On a later render, React reuses the cached value if every dependency is equal under Object.is. React’s documentation states that it “will not throw away the cached value unless there is a specific reason to do that,” which means the cache holds only as long as its inputs stay the same and React has no reason to discard it.
Rank #3
const visibleItems = useMemo(
() => filterItems(items, query),
[items, query]
);
Use it in two situations:
- The calculation is noticeably expensive, and its inputs often stay the same across renders (for example, a re-render triggered by an unrelated state change).
- You need a stable value to pass to a child wrapped in
memo, so the child can skip rendering.
Three constraints matter. The calculation must be pure. Dependencies must be complete; a missing dependency returns stale results. And useMemo does not make the first render faster, because nothing has been cached yet. If the calculation is cheap, or its inputs change on every keystroke, the cache rarely helps and only adds code.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Use memo for a child that measurement shows is expensive
memo(Component) lets React skip re-rendering a component when its props have not changed. By default, each prop is compared with Object.is. This is where most disappointing results come from. A new object, array, or function created during the parent’s render has a new identity every time, so the comparison fails and the child renders anyway.
const ResultRow = memo(function ResultRow({ item, onSelect }) {
return <li onClick={() => onSelect(item.id)}>{item.label}</li>;
});
Several limits apply. The component still re-renders when its own state changes or when a context it reads changes. A custom comparison function can avoid a re-render, but it runs on every render and can cost more than the render it prevents. React’s documentation for memo states that “memoization is a performance optimization, not a guarantee,” so treat a skipped render as a bonus that you verify in the Profiler, not something to rely on.
Rank #4
Keep input responsive with useTransition and useDeferredValue
Sometimes the work cannot be removed. A user types into a search box, and filtering a large list makes each keystroke slow. Both useTransition and useDeferredValue let React separate urgent updates from non-urgent ones, so the input can update first.
- Use
useTransitionwhen you control the state update that triggers the expensive render. Wrap that update instartTransition. - Use
useDeferredValuewhen the expensive part receives a value you do not set at the point of update, such as a prop or a value from a parent.
function SearchPage({ items }) {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const results = useMemo(
() => filterItems(items, deferredQuery),
[items, deferredQuery]
);
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ResultsList results={results} />
>
);
}
The tradeoff is visible to users. While urgent input is current, the deferred section can briefly show results for an older value. These hooks reprioritize rendering; they do not make the filter itself cheaper. If filtering is still slow after the input stays responsive, the calculation or the list rendering needs its own work.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsDefer code you do not need at first paint with lazy and Suspense
lazy delays loading a component’s code until the component is first rendered. Suspense shows a fallback while its children are loading. Together they reduce the initial bundle for features that most visitors do not open at once, such as a chart in a rarely visited dashboard tab.
Best Value
import { lazy, Suspense } from 'react';
const Chart = lazy(() => import('./Chart'));
function Dashboard() {
return (
<Suspense fallback={<p>Loading chart…</p>}>
<Chart />
</Suspense>
);
}
Place the boundary where the fallback makes sense to the user. A single boundary around a large page can hide much of the page behind one spinner, while several small boundaries can show content in pieces.
React 19 changes how suspended siblings are committed
The React 19 upgrade guide, published 2024-04-25, describes a change in how React handles suspense. When a component suspends, React can commit the nearest fallback without waiting for the entire sibling tree, and then schedule the suspended siblings to pre-warm their lazy requests. This is version-specific behavior: if your project is on an earlier React version, expect the older commit timing instead.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Compare the techniques by what they change
The table below puts the main patterns side by side. The key question is whether a technique removes work or only reprioritizes it.
Quick Recap
| Situation | Candidate pattern | What it changes | What must hold for it to help |
|---|---|---|---|
| A pure calculation is measurably slow and its inputs are stable | useMemo |
Reuses a calculated value across updates | Dependencies are complete and stable; the first render is unaffected |
| A child is costly and its props often stay the same | memo with stable props |
Can skip some child renders | Props keep their identity; the component’s own state and context still trigger renders |
| Typing or another urgent action competes with expensive UI work | useTransition or useDeferredValue |
Prioritizes urgent rendering over non-urgent rendering | The deferred section can briefly show older results |
| A rarely needed component adds to initial code size | lazy with Suspense |
Delays code loading and shows a fallback | The boundary and fallback suit the user’s flow; React 19 commit timing applies in React 19 projects |
| Repeated renders come from state updated inside Effects | Simplify state and Effects | Removes avoidable update chains | The value can be derived during render instead of stored in state |
Decision order for a slow screen
- Reproduce the slow interaction and record it in the Profiler, using a production build with CPU throttling where possible.
- Confirm whether React Compiler is enabled in your build.
- Remove avoidable updates: move state down, derive values during render, and simplify Effect chains.
- Classify what remains. A slow calculation points to
useMemo. A costly child with stable props points tomemo. Urgent input competing with expensive output points touseTransitionoruseDeferredValue. Large code that is not needed at first paint points tolazywithSuspense. - Apply one change at a time and re-measure the same interaction.
If the numbers do not improve
- Check that the expensive commit is the one you targeted. A sibling subtree may dominate the timing.
- Look for inline objects, arrays, or functions passed to memoized children. They silently defeat
memo. - Check the dependency list of each
useMemo. A missing dependency produces stale output, and an unnecessary one causes recalculation. - Remove a memoization that measured no gain. It adds code and comparison cost without benefit.
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.

