Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unwrap promise with experimental_use #40575

Merged
merged 3 commits into from Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/next/client/app-index.tsx
Expand Up @@ -14,6 +14,8 @@ declare global {
const __webpack_require__: any
}

const use = (React as any).experimental_use
huozhi marked this conversation as resolved.
Show resolved Hide resolved

// eslint-disable-next-line no-undef
const getChunkScriptFilename = __webpack_require__.u
const chunkFilenameMap: any = {}
Expand Down Expand Up @@ -143,7 +145,7 @@ function ServerRoot({ cacheKey }: { cacheKey: string }) {
rscCache.delete(cacheKey)
})
const response = useInitialServerResponse(cacheKey)
const root = response.readRoot()
const root = use(response)
return root
}

Expand Down
25 changes: 10 additions & 15 deletions packages/next/client/components/app-router.client.tsx
Expand Up @@ -65,8 +65,8 @@ export function fetchServerResponse(
url: URL,
flightRouterState: FlightRouterState,
prefetch?: true
): { readRoot: () => FlightData } {
// Handle the `fetch` readable stream that can be read using `readRoot`.
): Promise<FlightData> {
// Handle the `fetch` readable stream that can be unwrapped by `React.use`.
return createFromReadableStream(fetchFlight(url, flightRouterState, prefetch))
}

Expand Down Expand Up @@ -212,20 +212,15 @@ export default function AppRouter({
window.history.state?.tree || initialTree,
true
)
try {
r.readRoot()
} catch (e) {
await e
const flightData = r.readRoot()
// @ts-ignore startTransition exists
React.startTransition(() => {
dispatch({
type: ACTION_PREFETCH,
url,
flightData,
})
const flightData = await r
// @ts-ignore startTransition exists
React.startTransition(() => {
dispatch({
type: ACTION_PREFETCH,
url,
flightData,
})
}
})
} catch (err) {
console.error('PREFETCH ERROR', err)
}
Expand Down
8 changes: 5 additions & 3 deletions packages/next/client/components/layout-router.client.tsx
Expand Up @@ -17,6 +17,8 @@ import {
import { fetchServerResponse } from './app-router.client'
// import { matchSegment } from './match-segments'

const use = (React as any).experimental_use

/**
* Check if every segment in array a and b matches
*/
Expand Down Expand Up @@ -218,14 +220,14 @@ export function InnerLayoutRouter({
throw new Error('Child node should not have both subTreeData and data')
}

// If cache node has a data request we have to readRoot and update the cache.
// If cache node has a data request we have to unwrap response by `use` and update the cache.
if (childNode.data) {
// TODO-APP: error case
/**
* Flight response data
*/
// When the data has not resolved yet readRoot will suspend here.
const flightData = childNode.data.readRoot()
// When the data has not resolved yet `use` will suspend here.
const flightData = use(childNode.data)

// Handle case when navigating to page in `pages` from `app`
if (typeof flightData === 'string') {
Expand Down
12 changes: 7 additions & 5 deletions packages/next/client/components/reducer.ts
Expand Up @@ -6,9 +6,12 @@ import type {
FlightSegmentPath,
Segment,
} from '../../server/app-render'
import React from 'react'
import { matchSegment } from './match-segments'
import { fetchServerResponse } from './app-router.client'

const use = (React as any).experimental_use

/**
* Invalidate cache one level down from the router state.
*/
Expand Down Expand Up @@ -730,8 +733,7 @@ export function reducer(
cache,
state.cache,
segments.slice(1),
(): { readRoot: () => FlightData } =>
fetchServerResponse(url, optimisticTree)
(): Promise<FlightData> => fetchServerResponse(url, optimisticTree)
)

// If optimistic fetch couldn't happen it falls back to the non-optimistic case.
Expand Down Expand Up @@ -761,8 +763,8 @@ export function reducer(
cache.data = fetchServerResponse(url, state.tree)
}

// readRoot to suspend here (in the reducer) until the fetch resolves.
const flightData = cache.data.readRoot()
// Unwrap cache data with `use` to suspend here (in the reducer) until the fetch resolves.
const flightData = use(cache.data)

// Handle case when navigating to page in `pages` from `app`
if (typeof flightData === 'string') {
Expand Down Expand Up @@ -953,7 +955,7 @@ export function reducer(
'refetch',
])
}
const flightData = cache.data.readRoot()
const flightData = use(cache.data)

// Handle case when navigating to page in `pages` from `app`
if (typeof flightData === 'string') {
Expand Down
Expand Up @@ -162,6 +162,7 @@ function processErrorChunk(request, id, message, stack) {
return stringToChunk(row);
}
function processModelChunk(request, id, model) {
// $FlowFixMe: `json` might be `undefined` when model is a symbol.
var json = stringify(model, request.toJSON);
var row = serializeRowHeader('J', id) + json + '\n';
return stringToChunk(row);
Expand All @@ -172,6 +173,7 @@ function processReferenceChunk(request, id, reference) {
return stringToChunk(row);
}
function processModuleChunk(request, id, moduleMetaData) {
// $FlowFixMe: `json` might be `undefined` when moduleMetaData is a symbol.
var json = stringify(moduleMetaData);
var row = serializeRowHeader('M', id) + json + '\n';
return stringToChunk(row);
Expand Down Expand Up @@ -894,8 +896,8 @@ function readContext(context) {
return value;
}

// Corresponds to ReactFiberWakeable module. Generally, changes to one module
// should be reflected in the other.
// Corresponds to ReactFiberWakeable and ReactFizzWakeable modules. Generally,
// changes to one module should be reflected in the others.
// TODO: Rename this module and the corresponding Fiber one to "Thenable"
// instead of "Wakeable". Or some other more appropriate name.
// TODO: Sparse arrays are bad for performance.
Expand All @@ -917,11 +919,6 @@ function trackSuspendedWakeable(wakeable) {
// a listener that will update its status and result when it resolves.

switch (thenable.status) {
case 'pending':
// Since the status is already "pending", we can assume it will be updated
// when it resolves, either by React or something in userspace.
break;

case 'fulfilled':
case 'rejected':
// A thenable that already resolved shouldn't have been thrown, so this is
Expand All @@ -933,9 +930,13 @@ function trackSuspendedWakeable(wakeable) {

default:
{
// TODO: Only instrument the thenable if the status if not defined. If
// it's defined, but an unknown value, assume it's been instrumented by
// some custom userspace implementation.
if (typeof thenable.status === 'string') {
// Only instrument the thenable if the status if not defined. If
// it's defined, but an unknown value, assume it's been instrumented by
// some custom userspace implementation. We treat it as "pending".
break;
}

var pendingThenable = thenable;
pendingThenable.status = 'pending';
pendingThenable.then(function (fulfilledValue) {
Expand Down Expand Up @@ -990,7 +991,9 @@ function prepareToUseHooksForComponent(prevThenableState) {
thenableState = prevThenableState;
}
function getThenableStateAfterSuspending() {
return thenableState;
var state = thenableState;
thenableState = null;
return state;
}

function readContext$1(context) {
Expand Down Expand Up @@ -1149,6 +1152,9 @@ function use(usable) {
}
}
}
} else if (usable.$$typeof === REACT_SERVER_CONTEXT_TYPE) {
var context = usable;
return readContext$1(context);
}
} // eslint-disable-next-line react-internal/safe-string-coercion

Expand All @@ -1159,7 +1165,8 @@ function use(usable) {
var ContextRegistry = ReactSharedInternals.ContextRegistry;
function getOrCreateServerContext(globalName) {
if (!ContextRegistry[globalName]) {
ContextRegistry[globalName] = React.createServerContext(globalName, REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED);
ContextRegistry[globalName] = React.createServerContext(globalName, // $FlowFixMe function signature doesn't reflect the symbol value
REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED);
}

return ContextRegistry[globalName];
Expand Down Expand Up @@ -1292,7 +1299,8 @@ function attemptResolveElement(type, key, ref, props, prevThenableState) {
if (extraKeys.length !== 0) {
error('ServerContext can only have a value prop and children. Found: %s', JSON.stringify(extraKeys));
}
}
} // $FlowFixMe issue discovered when updating Flow


return [REACT_ELEMENT_TYPE, type, key, // Rely on __popProvider being serialized last to pop the provider.
{
Expand Down Expand Up @@ -1677,7 +1685,8 @@ function resolveModelToJSON(request, parent, key, value) {
}
}
}
}
} // $FlowFixMe


return value;
}
Expand Down Expand Up @@ -1708,12 +1717,14 @@ function resolveModelToJSON(request, parent, key, value) {

if (existingId !== undefined) {
return serializeByValueID(existingId);
}
} // $FlowFixMe `description` might be undefined


var name = value.description;
var name = value.description; // $FlowFixMe `name` might be undefined

if (Symbol.for(name) !== value) {
throw new Error('Only global symbols received from Symbol.for(...) can be passed to client components. ' + ("The symbol Symbol.for(" + value.description + ") cannot be found among global symbols. ") + ("Remove " + describeKeyForErrorMessage(key) + " from this object, or avoid the entire object: " + describeObjectForErrorMessage(parent)));
throw new Error('Only global symbols received from Symbol.for(...) can be passed to client components. ' + ("The symbol Symbol.for(" + // $FlowFixMe `description` might be undefined
value.description + ") cannot be found among global symbols. ") + ("Remove " + describeKeyForErrorMessage(key) + " from this object, or avoid the entire object: " + describeObjectForErrorMessage(parent)));
}

request.pendingChunks++;
Expand Down Expand Up @@ -1772,6 +1783,7 @@ function emitErrorChunk(request, id, error) {
}

function emitModuleChunk(request, id, moduleMetaData) {
// $FlowFixMe ModuleMetaData is not a ReactModel
var processedChunk = processModuleChunk(request, id, moduleMetaData);
request.completedModuleChunks.push(processedChunk);
}
Expand Down