skip to content

If a component starts its data request only after it has rendered once, what does the user wait for?

level: juniorimportance: must knowfreq 68%

answer

  1. render first, ask second
  2. the dataless frame is guaranteed
  3. the clock starts after commit
  4. each dependent level adds a trip

basics

~20 s

The first render happens with no data, because the request only starts once the component exists. The user waits for that dataless frame plus a full round trip, and every nested component that fetches repeats the pattern.

solid answer

~40 s

Declaring the request inside the component, in code that runs after render, fixes the ordering: render, commit, then ask. So the first frame is guaranteed to have no data, and the network clock starts *after* the screen was drawn rather than when the navigation began. The total wait is render time plus one full round trip, and the component renders again when the response lands. The cost compounds with nesting: a child that only renders once its parent has data cannot start its own request until the parent's response arrives, so two levels mean two sequential round trips. This is called **fetch-on-render** — the simplest thing to write, and the ordering that produces waterfalls.

go deeper

for a junior

Remember the fixed order: the component renders, the screen appears, then the request goes out, then a second render shows the data. That is why the first frame has no data.

for a middle

Explain the mechanics: the request lives in code that runs after commit, so it cannot start earlier, and a child that depends on a parent's response adds a second sequential round trip.

for a senior

Show that you measure from the user's action rather than from the server's view, and that you move a late request earlier instead of hiding it behind nicer placeholder art.

for a principal

Frame it as a default with a hidden cost: it is cheap per component and expensive per screen, so decide deliberately which data is allowed to be requested from inside the tree at all.

A component framework builds a tree of components, renders it, and commits the result to its host — a browser document, or a native view tree. A request declared *inside* a component, in the code the framework runs after that commit, cannot possibly leave the device earlier than the component itself. That single fact explains both the blank first frame and most of the waiting on a data-driven screen. ## The ordering, step by step 1. The framework renders the component with whatever it currently has. On a first visit that is nothing — no list, no record, no user. 2. The output is committed, so something appears on screen. 3. The after-render side effect runs, and only now does the request leave the device. 4. The response arrives, state is written, and the component renders a second time — this time with data. That order is **fetch-on-render**: render first, ask second. The dataless render is not a bug to optimise away; it is the direct consequence of putting the request behind the render. ## What the ordering costs - **A guaranteed extra render pass.** Every such screen renders at least twice: once without data, once with it. Whatever you show in between, the frame exists. - **A late start.** The request's clock starts after render, not when the user clicked. If the user clicked 120 ms ago and the response takes 200 ms, the perceived wait is the sum, not the 200 ms your server logs report. - **Nesting multiplies it.** If a child is rendered only once its parent has data, the child's request starts after the parent's response lands. Two dependent levels mean two sequential round trips — a **waterfall** — even when each request is individually fast. - **No single place knows what the screen needs.** Because each component declares its own request, nothing above the tree can start them all at once, and nothing can start them before the screen is reached. ## Where the request could start instead | Strategy | When the request starts | What is on screen while waiting | Waiting shape | |---|---|---|---| | Fetch-on-render | after the component renders | the new screen, without its data | one round trip per dependent level | | Fetch-then-render at a route boundary | before the destination renders | the previous screen, or a route-level placeholder | one wait in front of the whole screen | | Render-as-you-fetch | at navigation intent, before render | the destination filling in as parts arrive | only genuine data dependencies remain | The three differ purely in *when the request starts relative to rendering*. None of them makes the network faster; they move the wait to a different place and make a different part of it overlap with work the device was doing anyway. ## Why teams still write it this way Fetch-on-render is the default because it needs no coordination. The component that needs the data asks for it, ownership is obvious, and a widget nobody navigates to never fetches. It is a reasonable choice for data that only some interactions reveal — a panel opened on demand, a rarely expanded section — and for a screen where one request feeds everything and there is no second level to serialise. It becomes a problem when it is the *only* strategy in the codebase, because the cost is invisible in the code: nothing in a component that fetches its own data hints that its parent already spent a round trip getting the value it depends on. ## Model differences worth naming Frameworks disagree about where that after-render callback lives. A runtime that re-runs the component function on every state write hangs it off a declared side effect with a dependency list. A fine-grained runtime that tracks which values a computation read re-runs only the tracked reaction. A compile-time runtime rewrites the declaration into subscription code before it ever ships. The ordering consequence is identical in all three: a side effect that runs once the component is alive cannot start before the component is alive. ## How to answer this well - State the ordering explicitly — render, commit, request, re-render — rather than describing the symptom as a flash. - Separate the two costs: the extra render pass, and the round trip that started late. - Reach for the right fix. If the screen is slow because one request started late, move that request earlier — to the route boundary, or to the moment the user showed intent. If it is slow because three requests ran one after another, ask which of those dependencies is real. Adding a placeholder changes what the wait looks like, not how long it is.

  • Why can the same screen feel fast in a local test and slow to a real user when the request starts after render?
    Locally the round trip is a few milliseconds, so the late start is invisible and only the render cost shows. On a real connection the round trip dominates, and it is stacked on top of navigation and render time instead of overlapping them. The ordering defect was always there; latency is what makes it visible.
  • Does a screen whose components all fetch independently still suffer a waterfall?
    Not from the ordering between them: requests declared in components rendered in the same pass start together and overlap. The waterfall appears only where one component is rendered because another's data arrived. Independent siblings cost you the late start, not a chain.

Like phoning the restaurant only after you have sat down at the table: the sitting down was never the slow part, but nothing could be cooking while you did it.

saying these in an interview costs you the question

  • Claims data can be present on the very first render of the component
  • Treats the extra round trip as a slow-backend problem rather than a start-time problem
  • Thinks showing a placeholder shortens the wait instead of dressing it
  • Believes two independent requests started in the same render must run one after another
  • Measures only server response time and never when the request left the device