From be38f61c1104a1702b803bae5f274b079a0b0b4e Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 29 Sep 2022 02:21:41 +0200 Subject: [PATCH] Use throw to trigger Suspense to avoid use() in reducer warning (#40950) This is a temporarily fix as discussed with @sebmarkbage. It will be superseded by async reducer functions once that's added. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) --- packages/next/client/components/reducer.ts | 57 ++++++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/packages/next/client/components/reducer.ts b/packages/next/client/components/reducer.ts index 0ab7615663923..b1bb80536369c 100644 --- a/packages/next/client/components/reducer.ts +++ b/packages/next/client/components/reducer.ts @@ -7,10 +7,43 @@ import type { Segment, } from '../../server/app-render' // TODO-APP: change to React.use once it becomes stable -import { experimental_use as use } from 'react' import { matchSegment } from './match-segments' import { fetchServerResponse } from './app-router.client' +/** + * Create data fetching record for Promise. + */ +// TODO-APP: change `any` to type inference. +function createRecordFromThenable(thenable: any) { + thenable.status = 'pending' + thenable.then( + (value: any) => { + if (thenable.status === 'pending') { + thenable.status = 'fulfilled' + thenable.value = value + } + }, + (err: any) => { + if (thenable.status === 'pending') { + thenable.status = 'rejected' + thenable.value = err + } + } + ) + return thenable +} + +/** + * Read record value or throw Promise if it's not resolved yet. + */ +function readRecordValue(thenable: any) { + if (thenable.status === 'fulfilled') { + return thenable.value + } else { + throw thenable + } +} + function createHrefFromUrl(url: URL): string { return url.pathname + url.search + url.hash } @@ -781,11 +814,13 @@ function clientReducer( // If no in-flight fetch at the top, start it. if (!cache.data) { - cache.data = fetchServerResponse(url, state.tree) + cache.data = createRecordFromThenable( + fetchServerResponse(url, state.tree) + ) } // Unwrap cache data with `use` to suspend here (in the reducer) until the fetch resolves. - const [flightData, canonicalUrlOverride] = use(cache.data) + const [flightData, canonicalUrlOverride] = readRecordValue(cache.data) // Handle case when navigating to page in `pages` from `app` if (typeof flightData === 'string') { @@ -993,14 +1028,16 @@ function clientReducer( if (!cache.data) { // Fetch data from the root of the tree. - cache.data = fetchServerResponse(new URL(href, location.origin), [ - state.tree[0], - state.tree[1], - state.tree[2], - 'refetch', - ]) + cache.data = createRecordFromThenable( + fetchServerResponse(new URL(href, location.origin), [ + state.tree[0], + state.tree[1], + state.tree[2], + 'refetch', + ]) + ) } - const [flightData, canonicalUrlOverride] = use(cache.data) + const [flightData, canonicalUrlOverride] = readRecordValue(cache.data) // Handle case when navigating to page in `pages` from `app` if (typeof flightData === 'string') {