React Fetch and Render a List with Loading, Error and Empty States

intermediate
By FrontendAtlas Editorial · Updated Oct 17, 2026
intermediate level
React useEffect data fetching Discriminated-union state in TypeScript Effect cleanup and stale responses Loading, empty and error states Retry through a dependency change
Build a React component that loads a team list from a fake API and renders every request state: loading, success, empty, and error with a Retry button. Model the request as one status union in TypeScript, re-run the request when the data source or the retry counter changes, and ignore responses that arrive after the source has changed. Concepts: react, state, effects. React focus: useEffect data fetching with cleanup, discriminated-union state, and conditional rendering from a single status.
React useEffect data fetchingDiscriminated-union state in TypeScriptEffect cleanup and stale responsesLoading, empty and error statesRetry through a dependency change

Requirements

  • Render a data-source select (.mode) with the options ok, empty and flaky, and keep the chosen mode in state.
  • Call the provided fetchTeam(mode) fake API on mount and whenever the mode changes.
  • Hold the request in one TypeScript union: loading, success with the members, empty, or error with a message.
  • Show Loading team… while a request is in flight.
  • Render one .user-row per member with the name and role on success.
  • Show No team members yet. when the API returns an empty list.
  • Show a readable error line and a .retry button when the API rejects; Retry runs the request again with the current mode.
  • Ignore a response that arrives after the mode has changed again.

Expected behavior

  • On first render the loading state is visible, then six rows appear.
  • Choosing Empty team shows the empty state and no rows.
  • Choosing Flaky network shows the error line with a Retry button; clicking Retry loads the six rows.
  • Switching the source immediately shows the loading state and never shows rows from the previous source.
  • No any types: the fake API response, the request union and the select handler are typed.

Implementation notes

  • Use a discriminated union such as { status: 'loading' } | { status: 'success'; members: TeamMember[] } | { status: 'empty' } | { status: 'error'; message: string } instead of parallel booleans.
  • Put the request in a useEffect that depends on mode and a retry counter; set the loading state at the top of the effect.
  • Use a cancelled flag in the effect cleanup so a stale response cannot overwrite a newer state.
  • Convert unknown errors to a message with an instanceof Error check.

Solution idea

Keep mode, a retry attempt counter and one request union in state. A useEffect that depends on [mode, attempt] sets { status: 'loading' }, calls fetchTeam(mode) and maps the result: a non-empty list becomes success, an empty list becomes empty, a rejection becomes error with a readable message. The cleanup flips a cancelled flag so a response from a previous mode is ignored.

Public interview reports

These public, self-reported interview accounts informed this practice version. The exercise is modelled on reported rounds, not copied from them, and the reports are not verified official questions or endorsements.

  • Taro: Revolut frontend developer interview, April 2025 · Taro, April 2025

    One mid-level account describes a one-hour TypeScript live-coding task: fetch data from a source and render it with a loading state, error handling and types; the feedback named loading-state handling. This exercise is modelled on that round, not copied from it.

  • Taro: Revolut frontend engineer interview, May 2025 · Taro, May 2025

    One account describes a pairing session that used a service API with authentication to display data in a React component. This exercise practises the same fetch-and-render loop against a fake API; it is not the reported task.