Separate waiting, failure and obsolete data
An asynchronous interface has more than data or no data. It can be waiting, showing a previous result while updating, receiving failure or receiving a response no longer matching selection. First identify the resource and attempt each result belongs to.
A response that just arrived can be old from the user’s intent perspective. The example receives reportPromise and attemptId from its caller to make that relationship explicit. Do not infer success merely because a loading indicator disappears.
The contract should say which result was accepted, which failed and what action starts a new attempt.
What Suspense observes
Suspense supplies fallback when content integrated with its mechanism is not ready. Cases taught here include code loaded through lazy and reading a Promise with use. Starting fetch in an Effect or handler does not automatically activate the boundary.
If that flow uses its own state, it needs to represent loading and failure in that contract or use an appropriate framework integration. Naming a variable loading does not change this behavior. This lesson is limited to data and code reading; it does not depend on experimental features or React 19.3’s animation-specific visual-resource loading mechanisms.
Read a Promise with stable identity
use(reportPromise) returns the resolved value, suspends while the Promise is pending and routes rejection to the appropriate error boundary. The Promise needs caching so the same instance is reused across renders. Calling loadReport(id) on every render when that function always creates a new Promise does not meet that contract.
The example’s caller owns caching and new attempts. use must be called within a component or Hook and must not be wrapped in local try/catch to replace the suspension protocol. When a value travels from a Server Component to a Client Component, that value also has a serialization requirement.
Define the error boundary
An Error Boundary can replace a subtree that failed during rendering with an error presentation. In the example, getDerivedStateFromError purely updates state and render chooses fallback. componentDidCatch can add failure observation when needed.
The boundary does not protect against its own errors and does not catch ordinary handler errors, timer callback errors or server rendering through the same normal mechanism. Documentation distinguishes errors inside transition Actions as an exception.
Decide granularity from product needs: one report can fail without removing navigation that allows choosing another. A boundary around every small element is not automatically better architecture.
Out-of-order Effect results
In another design, an Effect starts a request when id changes. A starts first, B is selected later, but B responds before A. Always applying the last arriving response can display A’s body under B’s title.
The documented pattern uses a local guard invalidated during cleanup to avoid applying the earlier Effect’s result. That decision protects local acceptance; it does not prove transport or server work was canceled. The same reasoning should cover success and failure so an old error is not displayed over a new selection.
The presentation while the current request is unfinished also needs definition.
Recovery includes the resource
Displaying fallback contains failure rather than starting a new attempt. If cache keeps supplying the same rejected Promise, changing only the boundary key does not turn that Promise into success. Recovery must coordinate cache policy, the new attempt and necessary visual reset.
In the example, attemptId is meaningful only if it corresponds to the supplied resource attempt. Avoid a button that merely repeats the error without identifying what changed. Also distinguish recovering a read from repeating a write: this lesson does not establish that an external operation can be repeated without additional effects. Identify the operation before choosing retry.
Review an asynchronous view
Read the view at four moments: before data, with a valid result, with rejection and after selection changes. Check which boundary handles waiting, which handles errors and which interface parts remain available. Then confirm resource identity and response-acceptance rules.
An implementation can use a framework’s data mechanisms without React itself defining the entire cache or network policy. Assessments provide necessary premises and ask for the next correct behavior. The excerpt is a reading of component contracts; answering requires neither creating infrastructure nor executing requests.
import {Component, Suspense, use} from 'react';
class ReportErrorBoundary extends Component {
state = {failed: false};
static getDerivedStateFromError() { return {failed: true}; }
render() {
if (this.state.failed) return <p>Report unavailable. Choose a new attempt.</p>;
return this.props.children;
}
}
function ReportBody({reportPromise}) {
const report = use(reportPromise);
return <article><h2>{report.title}</h2><p>{report.summary}</p></article>;
}
// The caller owns caching/retry. Promise identity stays stable per attempt.
// A fulfilled report has string title and summary fields.
export default function ReportPanel({reportPromise, attemptId}) {
return <ReportErrorBoundary key={attemptId}>
<Suspense fallback={<p>Loading report...</p>}>
<ReportBody reportPromise={reportPromise} />
</Suspense>
</ReportErrorBoundary>;
}The caller supplies a stable Promise per attempt. While pending, use integrates the read with Suspense; rejection leads the Error Boundary to display failure. A new attempt must supply its corresponding resource and change attemptId when boundary reset is intended. The excerpt does not implement transport, caching or a retry button.
Common pitfalls
Expecting Suspense to observe every fetch; creating a Promise per render; wrapping use in try/catch; confusing ignored responses with server cancellation; resetting only the boundary while retaining a rejected Promise.
Related topics: Effects, refs and external synchronization · Performance, transitions and rendering decisions · Delivery, hydration and server boundaries
Async reading needs identity, waiting state, error boundaries and recovery policy. Suspense handles integrated reads; obsolete results and new attempts need explicit contracts.
Reference: Out-of-order Effect results · React 19.3.0; JSX