Skip to content

fix(react-query): hydrate disabled query shells in HydrationBoundary#10535

Closed
byungsker wants to merge 1 commit intoTanStack:mainfrom
byungsker:fix/issue-10145-hydration-boundary
Closed

fix(react-query): hydrate disabled query shells in HydrationBoundary#10535
byungsker wants to merge 1 commit intoTanStack:mainfrom
byungsker:fix/issue-10145-hydration-boundary

Conversation

@byungsker
Copy link
Copy Markdown
Contributor

@byungsker byungsker commented Apr 20, 2026

🎯 Changes

Fixes #10145

When a query with the same key already exists in the cache, HydrationBoundary currently defers hydration until the effect phase — which is correct for active queries during transitions.

However, this also applies to queries that are only a disabled idle shell with no data yet. In that case, a nested useSuspenseQuery can mount before the prefetched data is applied and unnecessarily suspend or refetch.

This PR makes HydrationBoundary hydrate existing queries immediately during render when they are idle, disabled, and have no data — treating them as effectively empty shells rather than active queries worth preserving.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes
    • Fixed HydrationBoundary to correctly handle hydration of disabled query shells, ensuring they are properly hydrated during the render phase instead of being deferred to after rendering.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

HydrationBoundary now intelligently routes dehydrated queries into the render phase when they target disabled or empty query shells, rather than always deferring hydration to the post-render effect. This prevents server-prefetched data from being skipped when parent components create disabled query cache entries with the same key.

Changes

Cohort / File(s) Summary
Documentation
.changeset/fix-hydration-boundary-disabled-shell.md
Patch-level changeset documenting the hydration boundary fix for disabled query shells.
Hydration Logic
packages/react-query/src/HydrationBoundary.tsx
Modified render-phase query selection to push newer dehydrated queries into newQueries (immediate hydration) when the existing cache entry is idle with no data or is disabled, instead of always deferring via existingQueries.
Core Hydration Tests
packages/query-core/src/__tests__/hydration.test.tsx
Added test verifying that hydrate() successfully populates data for a query that exists in cache as a disabled "query shell" created via QueryObserver with enabled: false.
Integration Tests
packages/react-query/src/__tests__/HydrationBoundary.test.tsx
Added integration test confirming that a disabled useQuery alongside a useSuspenseQuery with the same key properly hydrates during render without triggering client-side fetches.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A shell sits idle, cache so bare,
The hydration boundary whispers "I care!"
When disabled queries seek their place,
Render-phase magic fills the space.
No duplicates hop, no fetches stray,
Prefetched data wins the day! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: making HydrationBoundary hydrate disabled query shells instead of deferring hydration.
Description check ✅ Passed The PR description is complete with changes section, issue reference, checklist items marked, and release impact confirmed.
Linked Issues check ✅ Passed The PR successfully addresses issue #10145 by modifying HydrationBoundary to hydrate disabled idle query shells immediately during render rather than deferring to the effect phase.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the hydration issue: changeset documentation, test cases for disabled shells, and HydrationBoundary logic modification.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/issue-10145-hydration-boundary

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/react-query/src/__tests__/HydrationBoundary.test.tsx (1)

516-567: Good regression test; the clientQueryFn assertion is what actually proves render-phase hydration.

The key signal here is expect(clientQueryFn).toHaveBeenCalledTimes(0) — under the old behavior, Child's useSuspenseQuery would suspend before HydrationBoundary's effect ran, triggering a fetch. The cache-state toMatchObject assertion on its own doesn't distinguish render-phase vs effect-phase hydration (effects have already flushed by then), so it's good that the test pairs both.

One optional tweak: consider also asserting that Child did not throw/suspend beyond the synchronous render (e.g. queryByText('loading') is null) to make the render-phase guarantee more explicit in the test output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-query/src/__tests__/HydrationBoundary.test.tsx` around lines
516 - 567, Add an assertion that the Suspense fallback did not render
synchronously to make the render-phase hydration guarantee explicit: after
renderWithClient(...) and before clearing the client, query the rendered output
for the Suspense fallback text (e.g. "loading") and assert it is not present;
this should reference the existing Child component / useSuspenseQuery and the
clientQueryFn assertion so the test proves Child did not suspend during render
while HydrationBoundary applied the dehydrated state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/react-query/src/__tests__/HydrationBoundary.test.tsx`:
- Around line 516-567: Add an assertion that the Suspense fallback did not
render synchronously to make the render-phase hydration guarantee explicit:
after renderWithClient(...) and before clearing the client, query the rendered
output for the Suspense fallback text (e.g. "loading") and assert it is not
present; this should reference the existing Child component / useSuspenseQuery
and the clientQueryFn assertion so the test proves Child did not suspend during
render while HydrationBoundary applied the dehydrated state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 64a5774f-c3ca-4e98-8ed1-31e8a671d310

📥 Commits

Reviewing files that changed from the base of the PR and between cd6a274 and 7329a35.

📒 Files selected for processing (4)
  • .changeset/fix-hydration-boundary-disabled-shell.md
  • packages/query-core/src/__tests__/hydration.test.tsx
  • packages/react-query/src/HydrationBoundary.tsx
  • packages/react-query/src/__tests__/HydrationBoundary.test.tsx

@byungsker
Copy link
Copy Markdown
Contributor Author

Closing this as a duplicate of #10159, which was already opened by the issue author. Apologies for the noise!

@byungsker byungsker closed this Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HydrationBoundary ignores/skips server-prefetched query when a parent useQuery with same key is rendered before child useSuspenseQuery

1 participant