From bede8d7d1af0726aa4f8532675ff30308b9d831b Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Tue, 27 Sep 2022 14:59:53 +0200 Subject: [PATCH 1/2] Use throw to trigger Suspense to avoid use() in reducer warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a temporarily fix as discussed with @sebmarkbage. It will be superseded by async reducer functions once that's added. Co-Authored-By: Sebastian Markbåge --- packages/next/client/components/reducer.ts | 61 ++++++++++++++++++---- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/packages/next/client/components/reducer.ts b/packages/next/client/components/reducer.ts index 6052c849e038e..616b1528a157e 100644 --- a/packages/next/client/components/reducer.ts +++ b/packages/next/client/components/reducer.ts @@ -7,10 +7,47 @@ 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' +type Thenable = Promise & { + status: 'pending' | 'fulfilled' | 'rejected' + value: T +} + +/** + * Create data fetching record for Promise. + */ +function createRecordFromThenable(thenable: Thenable): Promise { + thenable.status = 'pending' + thenable.then( + (value) => { + if (thenable.status === 'pending') { + thenable.status = 'fulfilled' + thenable.value = value + } + }, + (err) => { + 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: Thenable): T { + if (thenable.status === 'fulfilled') { + return thenable.value + } else { + throw thenable + } +} + function createHrefFromUrl(url: URL): string { return url.pathname + url.search + url.hash } @@ -781,11 +818,13 @@ export function reducer( // 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 +1032,16 @@ export function reducer( 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') { From 80c935e28763e9051081ff98ca9ab498b36ecbc1 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 28 Sep 2022 11:24:08 +0200 Subject: [PATCH 2/2] Remove type inference --- packages/next/client/components/reducer.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/next/client/components/reducer.ts b/packages/next/client/components/reducer.ts index 616b1528a157e..8f816ff4a68ea 100644 --- a/packages/next/client/components/reducer.ts +++ b/packages/next/client/components/reducer.ts @@ -10,24 +10,20 @@ import type { import { matchSegment } from './match-segments' import { fetchServerResponse } from './app-router.client' -type Thenable = Promise & { - status: 'pending' | 'fulfilled' | 'rejected' - value: T -} - /** * Create data fetching record for Promise. */ -function createRecordFromThenable(thenable: Thenable): Promise { +// TODO-APP: change `any` to type inference. +function createRecordFromThenable(thenable: any) { thenable.status = 'pending' thenable.then( - (value) => { + (value: any) => { if (thenable.status === 'pending') { thenable.status = 'fulfilled' thenable.value = value } }, - (err) => { + (err: any) => { if (thenable.status === 'pending') { thenable.status = 'rejected' thenable.value = err @@ -40,7 +36,7 @@ function createRecordFromThenable(thenable: Thenable): Promise { /** * Read record value or throw Promise if it's not resolved yet. */ -function readRecordValue(thenable: Thenable): T { +function readRecordValue(thenable: any) { if (thenable.status === 'fulfilled') { return thenable.value } else {