React

Design loading and error states for a page with several API calls

Kamruz
Kamruz
Oct 14, 2026
5 min read
Design loading and error states for a page with several API calls

An account overview loads profile details, invoices, and usage metrics. A single Promise.all rejects because the metrics endpoint is unavailable, so the page hides a perfectly usable profile and invoice list. Another implementation shows “no invoices” while the invoice request has not finished.

These defects come from an unclear state contract. Several calls do not necessarily represent one indivisible result. This article uses an illustrative overview page to separate independent resources, dependencies, and product-level readiness so loading and error behavior matches what users can actually do.

Identify which data is essential

List the resources and the actions they enable. Profile identity may be essential for the page heading and account scope. Invoices may support billing review. Usage metrics may be supplementary. If metrics fail, users might still need access to invoices.

Determine whether any request depends on another's result. A resource identifier fetched from the profile may be necessary before requesting a detail panel. Independent calls can start together; dependent calls need a defined sequence or a combined server contract.

Avoid deciding solely by visual layout. Two cards displayed together can have independent failure states. A hidden permission record may be essential even though it has no card. The state design should follow behavior and trust, not the number of components.

Represent each resource coherently

Give each resource a status, data, error, and request identity or equivalent lifecycle structure. Loading, successful empty data, and failed data are distinct states. An empty array should not stand in for all three.

If keeping prior data during refresh, mark it as stale or refreshing according to the UI contract. Users should not mistake old usage numbers for a completed current request. Preserve useful content while making its freshness understandable.

Combine states only when the product truly needs an atomic result. A page that must show a consistent snapshot of several related values may benefit from one server endpoint or a snapshot identifier. Independent browser calls do not automatically provide that consistency.

Choose request aggregation deliberately

Promise.all is appropriate when any failure should fail the combined operation and all results are required. It does not cancel the other requests automatically when one rejects. Account for resource use and state handling if that behavior matters.

Promise.allSettled can collect independent outcomes, but it does not design the UI for you. Map each fulfilled or rejected result to its own resource state and keep error handling explicit. Do not turn every rejected call into empty data.

A query library may already provide per-resource states and cache behavior. Use its contracts consistently rather than maintaining competing local flags. Inspect retry, stale data, and request-key semantics so the page's presentation reflects what the library actually does.

Make partial failure actionable

Show the usable profile and invoices while the metrics panel explains its failure and offers a scoped retry. A page-wide error should be reserved for conditions that make the whole journey unusable, such as missing authentication or invalid account identity.

Write messages that name the affected resource without exposing internal details. “Usage data could not be loaded” is more useful than a generic “Something went wrong” across the entire screen. Keep a safe diagnostic identifier for support where appropriate.

Decide how permission denial differs from transient failure. A forbidden metrics panel may need a different message or no retry. Repeating a denied request wastes work. The server remains responsible for access control regardless of which card the UI hides.

Preserve ownership during navigation and retry

When the user switches accounts or date ranges, prior responses must not update the new page state. Key requests by the full resource identity and apply cancellation or current-request guards where needed.

A scoped retry should affect only the failed panel unless dependencies require more. Retrying metrics should not unnecessarily clear invoice data. If the retry uses a newer date range, associate its outcome with that range explicitly.

Guard errors and loading transitions as well as data writes. An old failure can overwrite a current success, and an old completion can remove the current spinner. The guide on request ordering gives a concrete implementation pattern for this ownership rule.

Use AI to enumerate page-state combinations

Provide the resource map and product actions to an assistant. Ask it to list combinations that matter: essential data loading, supplementary failure, empty invoices, refresh with old data, account switch, and retry during another pending request.

An illustrative request is: “Design states for profile, invoices, and metrics. Preserve invoice access when metrics fail. Distinguish empty from pending. Identify which errors invalidate the whole page and explain why.” This turns the assistant into a state-analysis aid rather than a generator of arbitrary skeletons.

Review the proposed combinations with the actual permissions and consistency requirements. The assistant may incorrectly treat an account denial as a local card failure. State presentation cannot override a trust boundary or pretend an incomplete snapshot is consistent.

Verify combinations rather than one happy path

Use controlled responses to test each resource independently. Resolve calls in different orders. Reject a supplementary call after essential data succeeds. Return an empty invoice list. Retry one panel. Navigate before a slow response completes.

Check accessibility and focus. A scoped retry should remain keyboard reachable, and updates should not move focus unexpectedly. Announce meaningful failure or completion where appropriate without repeatedly announcing every card change.

Keep the interface calm during refresh. Avoid replacing all content with skeletons if only one supplementary panel is updating. Conversely, do not show an old account's data while a new account loads. The distinction follows ownership and freshness, not a universal preference for preserving content.

A robust multi-request page exposes useful partial success, makes failures local when they are local, and treats essential dependencies honestly. The state model is the foundation; loading indicators and error copy are how users understand that model.

Document the server error contract that the page interprets. A timeout, authorization denial, missing account, and unavailable metrics provider may need different recovery. If every endpoint returns an unrelated envelope, a small normalization layer can make state handling more consistent without hiding the underlying categories. Verify that the layer preserves status and retryability rather than converting all errors into one generic object. This keeps the page model connected to real API outcomes.

Sources

MDN: Using Fetch documents request response behavior.

React: useEffect discusses lifecycle cleanup and fetching.

React: State as a Snapshot explains render state semantics.

About the Author

Kamruz

Kamruz

Full Stack Developer

A passionate developer who loves creating modern web applications and sharing knowledge with the community.

More About Me

Related Articles