segment cache: add test for passing unawaited promise to context provider (#89339)

Adds a failing test proving that if the RSC response from a prefetch
contains an unresolved promise, navigating to the page will cause the
Suspense boundary wrapping the use of that promise to remain in fallback
state indefinitely.

This appears to have regressed in
e9a03ac12c, though the theory is that this
is just uncovering a React bug rather than causing the issue.

Closes #89170
This commit is contained in:
Zack Tanner
2026-02-21 10:29:09 -08:00
committed by GitHub
parent ccee817b55
commit fd38d5bf15
10 changed files with 129 additions and 0 deletions
@@ -0,0 +1,9 @@
import { ReactNode } from 'react'
export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
@@ -0,0 +1,9 @@
import { ReactNode } from 'react'
import { getUser } from '../lib/get-user'
import { UserProvider } from '../user-provider'
export default function LearnLayout({ children }: { children: ReactNode }) {
const userPromise = getUser()
return <UserProvider userPromise={userPromise}>{children}</UserProvider>
}
@@ -0,0 +1,12 @@
import { UserCardShell } from '../user-card-shell'
export default async function LearnPage() {
'use cache'
return (
<main>
<h1>Learn</h1>
<UserCardShell />
</main>
)
}
@@ -0,0 +1,12 @@
import { cookies } from 'next/headers'
export async function getUser(): Promise<{ name: string }> {
const cookieStore = await cookies()
const userCookie = cookieStore.get('user')
if (!userCookie) {
return { name: 'Guest' }
}
return { name: userCookie.value }
}
@@ -0,0 +1,12 @@
import Link from 'next/link'
export default function Page() {
return (
<main>
<h1>User promise demo</h1>
<Link href="/learn" id="learn-link">
Go to learn
</Link>
</main>
)
}
@@ -0,0 +1,12 @@
'use client'
import { Suspense } from 'react'
import { UserCard } from './user-card'
export function UserCardShell() {
return (
<Suspense fallback={<p id="fallback">loading</p>}>
<UserCard />
</Suspense>
)
}
@@ -0,0 +1,9 @@
'use client'
import { useUser } from './user-provider'
export function UserCard() {
const user = useUser()
return <p id="user">user: {user?.name ?? 'unknown'}</p>
}
@@ -0,0 +1,29 @@
'use client'
import { createContext, ReactNode, use } from 'react'
type User = { name: string } | undefined
const UserContext = createContext<Promise<User> | null>(null)
UserContext.displayName = 'UserContext'
export function UserProvider({
children,
userPromise,
}: {
children: ReactNode
userPromise: Promise<User>
}) {
return (
<UserContext.Provider value={userPromise}>{children}</UserContext.Provider>
)
}
export function useUser(): User {
const userPromise = use(UserContext)
if (!userPromise) {
throw new Error('useUser must be used within a UserProvider')
}
return use(userPromise)
}
@@ -0,0 +1,8 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
cacheComponents: true,
}
module.exports = nextConfig
@@ -0,0 +1,17 @@
import { nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'
describe('prefetch-partial-rsc', () => {
const { next } = nextTestSetup({
files: __dirname,
})
it('resolves after a client-side navigation', async () => {
const browser = await next.browser('/')
await browser.elementByCss('#learn-link').click()
await retry(async () => {
const text = await browser.elementByCss('#user').text()
expect(text).toBe('user: Guest')
})
})
})