Skip to content

<Hydrate when={visible()}> throws React #467 when the gate resolves during a replayed mount #8652

Open

Description

@ethan-huo

Which project does this relate to?

Start

Describe the bug

<Hydrate when={visible()}> sometimes throws React #467 ("Update hook called on initial render") from HydrationBoundary in hydration/visible.tsx. During hydration it is followed by #422 and the boundary is client-rendered, so the server HTML and the deferred hydration are lost for that section.

HydrationBoundary suspends on the gate before it calls its only hook:

if (!g.r) {
  if (!reactUse) throw g.p
  reactUse(g.p)
}
React.useEffect(() => { o?.() }, [o])

If the gate resolves after use(g.p) suspends but before React abandons that render, React replays the suspended mount. The replay passes the gate and reaches useEffect, but the first attempt recorded no hooks and the fiber has no alternate, so React throws #467. It depends on timing, which is why it is intermittent in an app.

GenericHydrate avoids this: HydrationGate has no hooks and the effects live in HydratedBoundary.

Complete minimal reproducer

Unit test (jsdom, React 19), deterministic: https://github.com/ethan-huo/router/blob/fix/visible-hydration-hook-order/packages/react-start-client/src/tests/visible.test.tsx

A component rendered before the boundary reveals it from a microtask during a transition re-render, while the boundary is still showing its fallback. onRecoverableError receives "Update hook called on initial render" at visible.tsx, and React recovers with a synchronous render of the whole root.

Steps to Reproduce the Bug

  1. pnpm nx run @tanstack/react-start-client:test:unit -- src/tests/visible.test.tsx on main with the test above added.
  2. The test fails with the [Docs] API definitions and examples for 'createRouteConfig' #467 error collected from onRecoverableError.

In a production app with four visible() sections below the fold, Chrome logs #467 then #422 on 2–3 of 15 page loads.

Expected behavior

A visible() boundary never throws while it hydrates or mounts, and keeps its server HTML until it hydrates.

Platform

  • @tanstack/react-start-client 1.168.37 (unchanged on main at f6d21de)
  • react / react-dom 19.3.0
  • Chrome 155, macOS

Additional context

Fix: call the effect before the gate. With that change the app logs no #467/#422 in 25 of 25 loads. PR to follow.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions