Memoization does not freeze components
memo returns a component version that can avoid work when props are equivalent. By default, it compares each prop with Object.is. It does not perform unlimited deep comparison or guarantee absolute absence of rendering.
Own state and consumed context can still update the component. In the example, Results receives a rows snapshot and query string. If the parent constructs a new array every render, that identity also enters the decision.
Before adding memo, identify the work’s origin and which values actually change. A correct application must remain correct when React chooses to render again.
Cache a pure calculation
useMemo can reuse a calculation result when dependencies do not change. filterRows must remain pure and the list must include every reactive value used. In the example, filtering depends on rows and query. Omitting query retains old results rather than optimizing correctly.
React can discard the cache, including when a component suspends on initial mount. A user choice that must be retained therefore must not depend solely on that cache. Store the fact in state and derive results from it.
Not mutating data and being able to recalculate are correctness conditions; performance gains need separate evidence.
Comparators also preserve behavior
A custom arePropsEqual should declare equivalence only when output and behavior remain equivalent. Functions are props too: a new onSelect can capture another id or different parent state. If comparison ignores that change, the interface can retain an action using old data even when text looks identical.
Measure comparison cost as well. Deep traversal of a growing structure can cost more than the rendering it aims to avoid. Review needs two outcomes: preserved behavior and reduced total work. Counting avoided calls alone does not adequately establish either.
Transitions mark updates
startTransition calls its supplied function immediately and marks covered updates as transitions. It neither automatically moves heavy synchronous computation to a Web Worker nor turns it into a timer. Transition rendering work can be interrupted by more urgent updates such as text editing.
State controlling the input must remain immediate. In the documented version, a setter after await needs another startTransition to receive that marking. A call in a timer does not automatically inherit the earlier scope either.
Analyze where the update actually occurs and avoid inferring priority from a function name.
Present a deferred value
useDeferredValue lets part of the interface use an earlier value while work with the new value is prepared. In the example, query controls the input and deferredQuery feeds Results. Updating results makes explicit that the list can lag behind text.
The Hook does not impose its own fixed delay like debounce configured with an interval. Background work is interruptible. The Hook also does not itself prevent extra network requests.
If the product needs caching, cancellation or rate limiting, define those contracts in the layer initiating requests.
Priority does not decide logical order
Two asynchronous Actions can finish in the reverse order from which they started. Marking updates as transitions does not automatically tell React which response represents latest intent. Documentation identifies this limit across the asynchronous boundary.
The application needs an appropriate abstraction or explicit ordering and acceptance policy. For example, attempt identity can determine whether displaying a result remains valid, as in the previous lesson. Do not confuse keeping input responsive with preserving service-operation order. These are distinct objectives that can require distinct mechanisms.
Measure and justify the choice
An optimization proposal should identify the scenario, measured work and behavior that remains correct. Compare cost before and after under representative conditions and production mode because development checks can add calls. For a comparator, include comparison cost itself; for a filter, include data size and change frequency.
This lesson’s small example demonstrates responsibility separation rather than a measured gain. In assessment, select evidence capable of supporting the decision and recognize what the observation does not cover. A cache or transition is useful only when it improves the actual flow without retaining incorrect results or actions.
import {memo, useDeferredValue, useMemo, useState} from 'react';
const Results = memo(function Results({rows, query}) {
const visible = useMemo(() => {
const text = query.toLowerCase();
return rows.filter(row => row.title.toLowerCase().includes(text));
}, [rows, query]);
return <ul>{visible.map(row => <li key={row.id}>{row.title}</li>)}</ul>;
});
// rows is an immutable snapshot with stable unique IDs and string titles.
export default function SearchReview({rows}) {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const stale = query !== deferredQuery;
return <section>
<label>Search <input value={query}
onChange={event => setQuery(event.target.value)} /></label>
<p>{stale ? 'Updating results...' : `Results for: ${deferredQuery}`}</p>
<Results rows={rows} query={deferredQuery} />
</section>;
}The input follows query immediately. Results receives deferredQuery and can keep displaying the previous value while new work is prepared. Calculation uses rows and query without mutation.
The example is local and illustrative: it demonstrates neither measured gains nor reduced network requests.
Common pitfalls
Treating memo as freezing; relying on useMemo to store decisions; ignoring functions in a comparator; using transitions to control text; confusing useDeferredValue with debounce or cancellation; assuming request order after await.
Related topics: Reducers, context and shared state · Async data, Suspense and error boundaries · Testing behavior and evidence boundaries
Optimize observed work while preserving the functional contract. Memoization can reuse results; transitions and deferred values alter priority. None replaces retained state, network policy or result acceptance.
Reference: Memo comparison and guarantees · React 19.3.0; JSX