From 477c1a264e112e7088378dba4bb292f95f7f850f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 22 Nov 2020 21:22:09 +0000 Subject: [PATCH 01/13] Remove react/unstable_cache We're probably going to make it available via the dispatcher. Let's remove this for now. --- packages/react/package.json | 3 +- .../react/src/__tests__/ReactCache-test.js | 26 ----------- packages/react/src/cache/ReactCache.js | 43 ------------------- packages/react/unstable-cache.js | 9 ---- scripts/rollup/bundles.js | 9 ---- 5 files changed, 1 insertion(+), 89 deletions(-) delete mode 100644 packages/react/src/__tests__/ReactCache-test.js delete mode 100644 packages/react/src/cache/ReactCache.js delete mode 100644 packages/react/unstable-cache.js diff --git a/packages/react/package.json b/packages/react/package.json index 0dd2c35ee6284..87e5a806a21dd 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -17,8 +17,7 @@ "umd/", "jsx-runtime.js", "jsx-dev-runtime.js", - "unstable-index.server.js", - "unstable-cache.js" + "unstable-index.server.js" ], "main": "index.js", "exports": { diff --git a/packages/react/src/__tests__/ReactCache-test.js b/packages/react/src/__tests__/ReactCache-test.js deleted file mode 100644 index 3839d1a6b2e83..0000000000000 --- a/packages/react/src/__tests__/ReactCache-test.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails react-core - */ - -'use strict'; - -describe('ReactCache', () => { - let ReactCache; - - beforeEach(() => { - if (__EXPERIMENTAL__) { - ReactCache = require('react/unstable-cache'); - } - }); - - // TODO: test something useful. - // @gate experimental - it('exports something', () => { - expect(ReactCache.readCache).not.toBe(undefined); - }); -}); diff --git a/packages/react/src/cache/ReactCache.js b/packages/react/src/cache/ReactCache.js deleted file mode 100644 index d4240e1dca8ad..0000000000000 --- a/packages/react/src/cache/ReactCache.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * @flow - */ - -import type {ReactContext} from 'shared/ReactTypes'; - -import {createContext} from 'react'; -import invariant from 'shared/invariant'; - -type Cache = {| - resources: Map, -|}; - -// TODO: should there be a default cache? -const CacheContext: ReactContext = createContext(null); - -function CacheImpl() { - this.resources = new Map(); - // TODO: cancellation token. -} - -function createCache(): Cache { - // $FlowFixMe - return new CacheImpl(); -} - -function readCache(): Cache { - // TODO: this doesn't subscribe. - // But we really want load context anyway. - const value = CacheContext._currentValue; - if (value instanceof CacheImpl) { - return value; - } - invariant(false, 'Could not read the cache.'); -} - -const CacheProvider = CacheContext.Provider; - -export {createCache, readCache, CacheProvider}; diff --git a/packages/react/unstable-cache.js b/packages/react/unstable-cache.js deleted file mode 100644 index a6b90aa6728a5..0000000000000 --- a/packages/react/unstable-cache.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ -export {createCache, readCache, CacheProvider} from './src/cache/ReactCache'; diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 6f0f88e04e00b..c8244e8bb9a75 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -135,15 +135,6 @@ const bundles = [ externals: ['react'], }, - /******* React Cache (experimental, new) *******/ - { - bundleTypes: __EXPERIMENTAL__ ? [NODE_DEV, NODE_PROD, NODE_PROFILING] : [], - moduleType: ISOMORPHIC, - entry: 'react/unstable-cache', - global: 'ReactCache', - externals: ['react'], - }, - /******* React Fetch Browser (experimental, new) *******/ { bundleTypes: [NODE_DEV, NODE_PROD], From b7afa86f2db9f4840c3d0af5667ae377d0246d7a Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 22 Nov 2020 21:24:08 +0000 Subject: [PATCH 02/13] Add readContext() to the dispatcher On the server, it will be per-request. On the client, there will be some way to shadow it. For now, I provide it on the server, and throw on the client. --- .../react-debug-tools/src/ReactDebugHooks.js | 7 +++++ .../src/server/ReactPartialRendererHooks.js | 6 ++++ .../src/ReactFiberHooks.new.js | 30 +++++++++++++++++++ .../src/ReactFiberHooks.old.js | 30 +++++++++++++++++++ .../src/ReactInternalTypes.js | 3 +- .../react-server/src/ReactFlightServer.js | 21 +++++++++++++ packages/shared/ReactTypes.js | 5 ++++ scripts/error-codes/codes.json | 3 +- 8 files changed, 103 insertions(+), 2 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 39f8f5118c265..0f9b638bfeef2 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -13,6 +13,7 @@ import type { MutableSourceSubscribeFn, ReactContext, ReactProviderType, + ReactCache, } from 'shared/ReactTypes'; import type { Fiber, @@ -23,6 +24,7 @@ import type {OpaqueIDType} from 'react-reconciler/src/ReactFiberHostConfig'; import {NoMode} from 'react-reconciler/src/ReactTypeOfMode'; import ErrorStackParser from 'error-stack-parser'; +import invariant from 'shared/invariant'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols'; import { @@ -100,6 +102,10 @@ function nextHook(): null | Hook { return hook; } +function readCache(): ReactCache { + invariant(false, 'Not implemented.'); +} + function readContext( context: ReactContext, observedBits: void | number | boolean, @@ -298,6 +304,7 @@ function useOpaqueIdentifier(): OpaqueIDType | void { } const Dispatcher: DispatcherType = { + readCache, readContext, useCallback, useContext, diff --git a/packages/react-dom/src/server/ReactPartialRendererHooks.js b/packages/react-dom/src/server/ReactPartialRendererHooks.js index 49ef413357e7d..8c48b917e28c6 100644 --- a/packages/react-dom/src/server/ReactPartialRendererHooks.js +++ b/packages/react-dom/src/server/ReactPartialRendererHooks.js @@ -14,6 +14,7 @@ import type { MutableSourceGetSnapshotFn, MutableSourceSubscribeFn, ReactContext, + ReactCache, } from 'shared/ReactTypes'; import type PartialRenderer from './ReactPartialRenderer'; @@ -214,6 +215,10 @@ export function resetHooksState(): void { workInProgressHook = null; } +function readCache(): ReactCache { + invariant(false, 'Not implemented.'); +} + function readContext( context: ReactContext, observedBits: void | number | boolean, @@ -492,6 +497,7 @@ export function setCurrentPartialRenderer(renderer: PartialRenderer) { } export const Dispatcher: DispatcherType = { + readCache, readContext, useContext, useMemo, diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index 6f2cb9ca400c1..86a770442159f 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -12,6 +12,7 @@ import type { MutableSourceGetSnapshotFn, MutableSourceSubscribeFn, ReactContext, + ReactCache, } from 'shared/ReactTypes'; import type {Fiber, Dispatcher, HookType} from './ReactInternalTypes'; import type {Lanes, Lane} from './ReactFiberLane'; @@ -1815,7 +1816,12 @@ function dispatchAction( } } +function readCache() { + invariant(false, 'Not implemented.'); +} + export const ContextOnlyDispatcher: Dispatcher = { + readCache, readContext, useCallback: throwInvalidHookError, @@ -1837,6 +1843,7 @@ export const ContextOnlyDispatcher: Dispatcher = { }; const HooksDispatcherOnMount: Dispatcher = { + readCache, readContext, useCallback: mountCallback, @@ -1858,6 +1865,7 @@ const HooksDispatcherOnMount: Dispatcher = { }; const HooksDispatcherOnUpdate: Dispatcher = { + readCache, readContext, useCallback: updateCallback, @@ -1879,6 +1887,7 @@ const HooksDispatcherOnUpdate: Dispatcher = { }; const HooksDispatcherOnRerender: Dispatcher = { + readCache, readContext, useCallback: updateCallback, @@ -1927,6 +1936,9 @@ if (__DEV__) { }; HooksDispatcherOnMountInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2054,6 +2066,9 @@ if (__DEV__) { }; HooksDispatcherOnMountWithHookTypesInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2176,6 +2191,9 @@ if (__DEV__) { }; HooksDispatcherOnUpdateInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2298,6 +2316,9 @@ if (__DEV__) { }; HooksDispatcherOnRerenderInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2421,6 +2442,9 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnMountInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2558,6 +2582,9 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnUpdateInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2695,6 +2722,9 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnRerenderInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index 98e5d709cd285..f79f53fe9cef9 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -12,6 +12,7 @@ import type { MutableSourceGetSnapshotFn, MutableSourceSubscribeFn, ReactContext, + ReactCache, } from 'shared/ReactTypes'; import type {Fiber, Dispatcher, HookType} from './ReactInternalTypes'; import type {Lanes, Lane} from './ReactFiberLane'; @@ -1815,7 +1816,12 @@ function dispatchAction( } } +function readCache() { + invariant(false, 'Not implemented.'); +} + export const ContextOnlyDispatcher: Dispatcher = { + readCache, readContext, useCallback: throwInvalidHookError, @@ -1837,6 +1843,7 @@ export const ContextOnlyDispatcher: Dispatcher = { }; const HooksDispatcherOnMount: Dispatcher = { + readCache, readContext, useCallback: mountCallback, @@ -1858,6 +1865,7 @@ const HooksDispatcherOnMount: Dispatcher = { }; const HooksDispatcherOnUpdate: Dispatcher = { + readCache, readContext, useCallback: updateCallback, @@ -1879,6 +1887,7 @@ const HooksDispatcherOnUpdate: Dispatcher = { }; const HooksDispatcherOnRerender: Dispatcher = { + readCache, readContext, useCallback: updateCallback, @@ -1927,6 +1936,9 @@ if (__DEV__) { }; HooksDispatcherOnMountInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2054,6 +2066,9 @@ if (__DEV__) { }; HooksDispatcherOnMountWithHookTypesInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2176,6 +2191,9 @@ if (__DEV__) { }; HooksDispatcherOnUpdateInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2298,6 +2316,9 @@ if (__DEV__) { }; HooksDispatcherOnRerenderInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2421,6 +2442,9 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnMountInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2558,6 +2582,9 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnUpdateInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2695,6 +2722,9 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnRerenderInDEV = { + readCache(): ReactCache { + return readCache(); + }, readContext( context: ReactContext, observedBits: void | number | boolean, diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index f63495cdce655..d2a0c7a822708 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -23,7 +23,7 @@ import type {Flags} from './ReactFiberFlags'; import type {Lane, LanePriority, Lanes, LaneMap} from './ReactFiberLane'; import type {RootTag} from './ReactRootTags'; import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig'; -import type {Wakeable} from 'shared/ReactTypes'; +import type {Wakeable, ReactCache} from 'shared/ReactTypes'; import type {Interaction} from 'scheduler/src/Tracing'; // Unwind Circular: moved from ReactFiberHooks.old @@ -274,6 +274,7 @@ type BasicStateAction = (S => S) | S; type Dispatch = A => void; export type Dispatcher = {| + readCache(): ReactCache, readContext( context: ReactContext, observedBits: void | number | boolean, diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 374ce95397f35..8f1f0e37ddc7d 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -40,6 +40,7 @@ import { REACT_LAZY_TYPE, REACT_MEMO_TYPE, } from 'shared/ReactSymbols'; +import type {ReactCache} from 'shared/ReactTypes'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import invariant from 'shared/invariant'; @@ -74,6 +75,7 @@ type Segment = { export type Request = { destination: Destination, bundlerConfig: BundlerConfig, + cache: ReactCache, nextChunkId: number, pendingChunks: number, pingedSegments: Array, @@ -88,6 +90,12 @@ export type Request = { const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher; +function createCache(): ReactCache { + return { + resources: new Map(), + }; +} + export function createRequest( model: ReactModel, destination: Destination, @@ -97,6 +105,7 @@ export function createRequest( const request = { destination, bundlerConfig, + cache: createCache(), nextChunkId: 0, pendingChunks: 0, pingedSegments: pingedSegments, @@ -652,7 +661,9 @@ function retrySegment(request: Request, segment: Segment): void { function performWork(request: Request): void { const prevDispatcher = ReactCurrentDispatcher.current; + const prevCache = currentCache; ReactCurrentDispatcher.current = Dispatcher; + currentCache = request.cache; const pingedSegments = request.pingedSegments; request.pingedSegments = []; @@ -665,6 +676,7 @@ function performWork(request: Request): void { } ReactCurrentDispatcher.current = prevDispatcher; + currentCache = prevCache; } let reentrant = false; @@ -743,6 +755,8 @@ function unsupportedHook(): void { invariant(false, 'This Hook is not supported in Server Components.'); } +let currentCache: ReactCache | null = null; + const Dispatcher: DispatcherType = { useMemo(nextCreate: () => T): T { return nextCreate(); @@ -757,6 +771,13 @@ const Dispatcher: DispatcherType = { useTransition(): [(callback: () => void) => void, boolean] { return [() => {}, false]; }, + readCache() { + invariant( + currentCache, + 'Reading the cache is only supported while rendering.', + ); + return currentCache; + }, readContext: (unsupportedHook: any), useContext: (unsupportedHook: any), useReducer: (unsupportedHook: any), diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index c03ab5056f6f4..7cb564f16a211 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -216,3 +216,8 @@ export interface Thenable<+R> { onReject: (error: mixed) => void | Thenable | U, ): void | Thenable; } + +export type ReactCache = {| + // TODO: a different API. + resources: Map, +|}; diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 269b7d2787727..c1a61c34cfaa5 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -367,5 +367,6 @@ "376": "Only global symbols received from Symbol.for(...) can be passed to client components. The symbol Symbol.for(%s) cannot be found among global symbols. Remove %s from this object, or avoid the entire object: %s", "377": "BigInt (%s) is not yet supported in client component props. Remove %s from this object or use a plain number instead: %s", "378": "Type %s is not supported in client component props. Remove %s from this object, or avoid the entire object: %s", - "379": "Refs cannot be used in server components, nor passed to client components." + "379": "Refs cannot be used in server components, nor passed to client components.", + "380": "Reading the cache is only supported while rendering." } From 1426ec42084dfad903f71db980381604ca2b03d5 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 22 Nov 2020 21:25:51 +0000 Subject: [PATCH 03/13] Use readContext() from react-fetch This makes it work on the server (but not on the client until we implement it there.) Updated the test to use Server Components. Now it passes. --- packages/react-fetch/src/ReactFetchBrowser.js | 12 +- packages/react-fetch/src/ReactFetchNode.js | 13 +- .../src/__tests__/ReactFetchNode-test.js | 191 +++++++++++++----- 3 files changed, 165 insertions(+), 51 deletions(-) diff --git a/packages/react-fetch/src/ReactFetchBrowser.js b/packages/react-fetch/src/ReactFetchBrowser.js index af073733b8286..b67e6319a2468 100644 --- a/packages/react-fetch/src/ReactFetchBrowser.js +++ b/packages/react-fetch/src/ReactFetchBrowser.js @@ -7,9 +7,9 @@ * @flow */ -import type {Wakeable} from 'shared/ReactTypes'; +import type {Wakeable, ReactCache} from 'shared/ReactTypes'; -import {readCache} from 'react/unstable-cache'; +import * as React from 'react'; const Pending = 0; const Resolved = 1; @@ -36,6 +36,14 @@ type Result = PendingResult | ResolvedResult | RejectedResult; const nativeFetch = window.fetch; const fetchKey = {}; +const ReactCurrentDispatcher = + React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED + .ReactCurrentDispatcher; + +function readCache(): ReactCache { + return ReactCurrentDispatcher.current.readCache(); +} + function readResultMap(): Map { const resources = readCache().resources; let map = resources.get(fetchKey); diff --git a/packages/react-fetch/src/ReactFetchNode.js b/packages/react-fetch/src/ReactFetchNode.js index e082fbaf37f07..57f14ea40017c 100644 --- a/packages/react-fetch/src/ReactFetchNode.js +++ b/packages/react-fetch/src/ReactFetchNode.js @@ -7,12 +7,11 @@ * @flow */ -import type {Wakeable} from 'shared/ReactTypes'; +import type {Wakeable, ReactCache} from 'shared/ReactTypes'; import * as http from 'http'; import * as https from 'https'; - -import {readCache} from 'react/unstable-cache'; +import * as React from 'react'; type FetchResponse = {| // Properties @@ -75,6 +74,14 @@ type RejectedResult = {| type Result = PendingResult | ResolvedResult | RejectedResult; +const ReactCurrentDispatcher = + React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED + .ReactCurrentDispatcher; + +function readCache(): ReactCache { + return ReactCurrentDispatcher.current.readCache(); +} + const fetchKey = {}; function readResultMap(): Map> { diff --git a/packages/react-fetch/src/__tests__/ReactFetchNode-test.js b/packages/react-fetch/src/__tests__/ReactFetchNode-test.js index 1588554972691..537d4a993b67e 100644 --- a/packages/react-fetch/src/__tests__/ReactFetchNode-test.js +++ b/packages/react-fetch/src/__tests__/ReactFetchNode-test.js @@ -9,9 +9,21 @@ 'use strict'; +// Polyfills for test environment +global.ReadableStream = require('@mattiasbuelens/web-streams-polyfill/ponyfill/es6').ReadableStream; +global.TextDecoder = require('util').TextDecoder; + +// Don't wait before processing work on the server. +// TODO: we can replace this with FlightServer.act(). +global.setImmediate = cb => cb(); + describe('ReactFetchNode', () => { - let ReactCache; - let ReactFetchNode; + let React; + let ReactDOM; + let ReactTransportDOMServer; + let ReactTransportDOMClient; + let Stream; + let act; let http; let fetch; let server; @@ -20,20 +32,24 @@ describe('ReactFetchNode', () => { beforeEach(done => { jest.resetModules(); - if (__EXPERIMENTAL__) { - ReactCache = require('react/unstable-cache'); - // TODO: A way to pass load context. - ReactCache.CacheProvider._context._currentValue = ReactCache.createCache(); - ReactFetchNode = require('react-fetch'); - fetch = ReactFetchNode.fetch; - } + + Stream = require('stream'); + React = require('react'); + ReactDOM = require('react-dom'); + ReactTransportDOMServer = require('react-transport-dom-webpack/server'); + ReactTransportDOMClient = require('react-transport-dom-webpack'); + act = require('react-dom/test-utils').act; + fetch = require('react-fetch').fetch; http = require('http'); server = http.createServer((req, res) => { serverImpl(req, res); }); - server.listen(done); - serverEndpoint = `http://localhost:${server.address().port}/`; + serverEndpoint = null; + server.listen(() => { + serverEndpoint = `http://localhost:${server.address().port}/`; + done(); + }); }); afterEach(done => { @@ -41,55 +57,138 @@ describe('ReactFetchNode', () => { server = null; }); - async function waitForSuspense(fn) { - while (true) { - try { - return fn(); - } catch (promise) { - if (typeof promise.then === 'function') { - await promise; - } else { - throw promise; - } - } + function getTestStream() { + const writable = new Stream.PassThrough(); + const readable = new ReadableStream({ + start(controller) { + writable.on('data', chunk => { + controller.enqueue(chunk); + }); + writable.on('end', () => { + controller.close(); + }); + }, + }); + return { + writable, + readable, + }; + } + + async function getServerOutput(serverTree) { + const {writable, readable} = getTestStream(); + ReactTransportDOMServer.pipeToNodeWritable(serverTree, writable, {}); + const response = ReactTransportDOMClient.createFromReadableStream(readable); + + const container = document.createElement('div'); + const root = ReactDOM.unstable_createRoot(container); + + function Client() { + return response.readRoot(); + } + + await act(async () => { + root.render( + + + , + ); + }); + while (container.innerHTML === 'loading...') { + await act(async () => {}); } + return container.innerHTML; } // @gate experimental - it('can read text', async () => { + it('can fetch text from a server component', async () => { serverImpl = (req, res) => { - res.write('ok'); + res.write('mango'); res.end(); }; - await waitForSuspense(() => { - const response = fetch(serverEndpoint); - expect(response.status).toBe(200); - expect(response.statusText).toBe('OK'); - expect(response.ok).toBe(true); - expect(response.text()).toEqual('ok'); - // Can read again: - expect(response.text()).toEqual('ok'); - }); + function App() { + const text = fetch(serverEndpoint).text(); + return
{text.toUpperCase()}
; + } + const output = await getServerOutput(); + expect(output).toEqual('
MANGO
'); }); // @gate experimental - it('can read json', async () => { + it('can fetch json from a server component', async () => { serverImpl = (req, res) => { res.write(JSON.stringify({name: 'Sema'})); res.end(); }; - await waitForSuspense(() => { + function App() { + const json = fetch(serverEndpoint).json(); + return
{json.name.toUpperCase()}
; + } + const output = await getServerOutput(); + expect(output).toEqual('
SEMA
'); + }); + + // @gate experimental + it('provides response status', async () => { + serverImpl = (req, res) => { + res.write(JSON.stringify({name: 'Sema'})); + res.end(); + }; + function App() { const response = fetch(serverEndpoint); - expect(response.status).toBe(200); - expect(response.statusText).toBe('OK'); - expect(response.ok).toBe(true); - expect(response.json()).toEqual({ - name: 'Sema', - }); - // Can read again: - expect(response.json()).toEqual({ - name: 'Sema', - }); - }); + return ( +
+ {response.status} {response.statusText} {'' + response.ok} +
+ ); + } + const output = await getServerOutput(); + expect(output).toEqual('
200 OK true
'); + }); + + // @gate experimental + it('handles different paths', async () => { + serverImpl = (req, res) => { + switch (req.url) { + case '/banana': + res.write('banana'); + break; + case '/mango': + res.write('mango'); + break; + case '/orange': + res.write('orange'); + break; + } + res.end(); + }; + function Banana() { + return {fetch(serverEndpoint + 'banana').text()}; + } + function Mango() { + return {fetch(serverEndpoint + 'mango').text()}; + } + function Orange() { + return {fetch(serverEndpoint + 'orange').text()}; + } + function App() { + return ( +
+ + + + +
+ ); + } + const output = await getServerOutput(); + expect(output).toEqual( + '
' + + 'banana' + + 'mango' + + 'orange' + + 'mango' + + '
', + ); }); }); From 38de6e6837a9a8a27cfaf7a07b6446b0aaea94a4 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 22 Nov 2020 21:32:09 +0000 Subject: [PATCH 04/13] Fixture: Add fetch from a Server Component --- fixtures/flight/server/cli.server.js | 21 ++++++++++++++++++--- fixtures/flight/src/App.server.js | 7 +++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/fixtures/flight/server/cli.server.js b/fixtures/flight/server/cli.server.js index 00dc4815b7287..3d28e818bcf83 100644 --- a/fixtures/flight/server/cli.server.js +++ b/fixtures/flight/server/cli.server.js @@ -17,13 +17,28 @@ const app = express(); // Application app.get('/', function(req, res) { if (process.env.NODE_ENV === 'development') { - for (var key in require.cache) { - delete require.cache[key]; - } + // This doesn't work in ESM mode. + // for (var key in require.cache) { + // delete require.cache[key]; + // } } require('./handler.server.js')(req, res); }); +app.get('/todos', function(req, res) { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.json([ + { + id: 1, + text: 'Shave yaks', + }, + { + id: 2, + text: 'Eat kale', + }, + ]); +}); + app.listen(3001, () => { console.log('Flight Server listening on port 3001...'); }); diff --git a/fixtures/flight/src/App.server.js b/fixtures/flight/src/App.server.js index 35a223dce8b07..4a22318e9f18d 100644 --- a/fixtures/flight/src/App.server.js +++ b/fixtures/flight/src/App.server.js @@ -1,4 +1,5 @@ import * as React from 'react'; +import {fetch} from 'react-fetch'; import Container from './Container.js'; @@ -8,11 +9,17 @@ import {Counter as Counter2} from './Counter2.client.js'; import ShowMore from './ShowMore.client.js'; export default function App() { + const todos = fetch('http://localhost:3001/todos').json(); return (

Hello, world

+
    + {todos.map(todo => ( +
  • {todo.text}
  • + ))} +

Lorem ipsum

From 1e7e5a985a98faddea5e0b951300f7baafb9884d Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 1 Dec 2020 18:56:23 +0000 Subject: [PATCH 05/13] readCache -> getCacheForType --- .../react-debug-tools/src/ReactDebugHooks.js | 5 +-- .../src/server/ReactPartialRendererHooks.js | 5 +-- packages/react-fetch/src/ReactFetchBrowser.js | 19 +++------ packages/react-fetch/src/ReactFetchNode.js | 20 +++------- .../src/ReactFiberHooks.new.js | 39 +++++++++---------- .../src/ReactFiberHooks.old.js | 39 +++++++++---------- .../src/ReactInternalTypes.js | 4 +- .../react-server/src/ReactFlightServer.js | 22 +++++------ packages/shared/ReactTypes.js | 5 --- 9 files changed, 66 insertions(+), 92 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 0f9b638bfeef2..ce62b5ce8b9ef 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -13,7 +13,6 @@ import type { MutableSourceSubscribeFn, ReactContext, ReactProviderType, - ReactCache, } from 'shared/ReactTypes'; import type { Fiber, @@ -102,7 +101,7 @@ function nextHook(): null | Hook { return hook; } -function readCache(): ReactCache { +function getCacheForType(resourceType: () => T): T { invariant(false, 'Not implemented.'); } @@ -304,7 +303,7 @@ function useOpaqueIdentifier(): OpaqueIDType | void { } const Dispatcher: DispatcherType = { - readCache, + getCacheForType, readContext, useCallback, useContext, diff --git a/packages/react-dom/src/server/ReactPartialRendererHooks.js b/packages/react-dom/src/server/ReactPartialRendererHooks.js index 8c48b917e28c6..1ff3ab800ac4f 100644 --- a/packages/react-dom/src/server/ReactPartialRendererHooks.js +++ b/packages/react-dom/src/server/ReactPartialRendererHooks.js @@ -14,7 +14,6 @@ import type { MutableSourceGetSnapshotFn, MutableSourceSubscribeFn, ReactContext, - ReactCache, } from 'shared/ReactTypes'; import type PartialRenderer from './ReactPartialRenderer'; @@ -215,7 +214,7 @@ export function resetHooksState(): void { workInProgressHook = null; } -function readCache(): ReactCache { +function getCacheForType(resourceType: () => T): T { invariant(false, 'Not implemented.'); } @@ -497,7 +496,7 @@ export function setCurrentPartialRenderer(renderer: PartialRenderer) { } export const Dispatcher: DispatcherType = { - readCache, + getCacheForType, readContext, useContext, useMemo, diff --git a/packages/react-fetch/src/ReactFetchBrowser.js b/packages/react-fetch/src/ReactFetchBrowser.js index b67e6319a2468..e6e3f369d5532 100644 --- a/packages/react-fetch/src/ReactFetchBrowser.js +++ b/packages/react-fetch/src/ReactFetchBrowser.js @@ -7,7 +7,7 @@ * @flow */ -import type {Wakeable, ReactCache} from 'shared/ReactTypes'; +import type {Wakeable} from 'shared/ReactTypes'; import * as React from 'react'; @@ -34,24 +34,17 @@ type Result = PendingResult | ResolvedResult | RejectedResult; // TODO: this is a browser-only version. Add a separate Node entry point. const nativeFetch = window.fetch; -const fetchKey = {}; const ReactCurrentDispatcher = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED .ReactCurrentDispatcher; -function readCache(): ReactCache { - return ReactCurrentDispatcher.current.readCache(); +function getResultMap(): Map { + return ReactCurrentDispatcher.current.getCacheForType(createResultMap); } -function readResultMap(): Map { - const resources = readCache().resources; - let map = resources.get(fetchKey); - if (map === undefined) { - map = new Map(); - resources.set(fetchKey, map); - } - return map; +function createResultMap(): Map { + return new Map(); } function toResult(thenable): Result { @@ -128,7 +121,7 @@ Response.prototype = { }; function preloadResult(url: string, options: mixed): Result { - const map = readResultMap(); + const map = getResultMap(); let entry = map.get(url); if (!entry) { if (options) { diff --git a/packages/react-fetch/src/ReactFetchNode.js b/packages/react-fetch/src/ReactFetchNode.js index 57f14ea40017c..7edb51c249d1e 100644 --- a/packages/react-fetch/src/ReactFetchNode.js +++ b/packages/react-fetch/src/ReactFetchNode.js @@ -7,7 +7,7 @@ * @flow */ -import type {Wakeable, ReactCache} from 'shared/ReactTypes'; +import type {Wakeable} from 'shared/ReactTypes'; import * as http from 'http'; import * as https from 'https'; @@ -78,20 +78,12 @@ const ReactCurrentDispatcher = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED .ReactCurrentDispatcher; -function readCache(): ReactCache { - return ReactCurrentDispatcher.current.readCache(); +function getResultMap(): Map> { + return ReactCurrentDispatcher.current.getCacheForType(createResultMap); } -const fetchKey = {}; - -function readResultMap(): Map> { - const resources = readCache().resources; - let map = resources.get(fetchKey); - if (map === undefined) { - map = new Map(); - resources.set(fetchKey, map); - } - return map; +function createResultMap(): Map> { + return new Map(); } function readResult(result: Result): T { @@ -173,7 +165,7 @@ Response.prototype = { }; function preloadResult(url: string, options: mixed): Result { - const map = readResultMap(); + const map = getResultMap(); let entry = map.get(url); if (!entry) { if (options) { diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index 86a770442159f..b8a5faa2a18ac 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -12,7 +12,6 @@ import type { MutableSourceGetSnapshotFn, MutableSourceSubscribeFn, ReactContext, - ReactCache, } from 'shared/ReactTypes'; import type {Fiber, Dispatcher, HookType} from './ReactInternalTypes'; import type {Lanes, Lane} from './ReactFiberLane'; @@ -1816,12 +1815,12 @@ function dispatchAction( } } -function readCache() { +function getCacheForType(resourceType: () => T): T { invariant(false, 'Not implemented.'); } export const ContextOnlyDispatcher: Dispatcher = { - readCache, + getCacheForType, readContext, useCallback: throwInvalidHookError, @@ -1843,7 +1842,7 @@ export const ContextOnlyDispatcher: Dispatcher = { }; const HooksDispatcherOnMount: Dispatcher = { - readCache, + getCacheForType, readContext, useCallback: mountCallback, @@ -1865,7 +1864,7 @@ const HooksDispatcherOnMount: Dispatcher = { }; const HooksDispatcherOnUpdate: Dispatcher = { - readCache, + getCacheForType, readContext, useCallback: updateCallback, @@ -1887,7 +1886,7 @@ const HooksDispatcherOnUpdate: Dispatcher = { }; const HooksDispatcherOnRerender: Dispatcher = { - readCache, + getCacheForType, readContext, useCallback: updateCallback, @@ -1936,8 +1935,8 @@ if (__DEV__) { }; HooksDispatcherOnMountInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2066,8 +2065,8 @@ if (__DEV__) { }; HooksDispatcherOnMountWithHookTypesInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2191,8 +2190,8 @@ if (__DEV__) { }; HooksDispatcherOnUpdateInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2316,8 +2315,8 @@ if (__DEV__) { }; HooksDispatcherOnRerenderInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2442,8 +2441,8 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnMountInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2582,8 +2581,8 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnUpdateInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2722,8 +2721,8 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnRerenderInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index f79f53fe9cef9..3f0017147ae76 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -12,7 +12,6 @@ import type { MutableSourceGetSnapshotFn, MutableSourceSubscribeFn, ReactContext, - ReactCache, } from 'shared/ReactTypes'; import type {Fiber, Dispatcher, HookType} from './ReactInternalTypes'; import type {Lanes, Lane} from './ReactFiberLane'; @@ -1816,12 +1815,12 @@ function dispatchAction( } } -function readCache() { +function getCacheForType(resourceType: () => T): T { invariant(false, 'Not implemented.'); } export const ContextOnlyDispatcher: Dispatcher = { - readCache, + getCacheForType, readContext, useCallback: throwInvalidHookError, @@ -1843,7 +1842,7 @@ export const ContextOnlyDispatcher: Dispatcher = { }; const HooksDispatcherOnMount: Dispatcher = { - readCache, + getCacheForType, readContext, useCallback: mountCallback, @@ -1865,7 +1864,7 @@ const HooksDispatcherOnMount: Dispatcher = { }; const HooksDispatcherOnUpdate: Dispatcher = { - readCache, + getCacheForType, readContext, useCallback: updateCallback, @@ -1887,7 +1886,7 @@ const HooksDispatcherOnUpdate: Dispatcher = { }; const HooksDispatcherOnRerender: Dispatcher = { - readCache, + getCacheForType, readContext, useCallback: updateCallback, @@ -1936,8 +1935,8 @@ if (__DEV__) { }; HooksDispatcherOnMountInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2066,8 +2065,8 @@ if (__DEV__) { }; HooksDispatcherOnMountWithHookTypesInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2191,8 +2190,8 @@ if (__DEV__) { }; HooksDispatcherOnUpdateInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2316,8 +2315,8 @@ if (__DEV__) { }; HooksDispatcherOnRerenderInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2442,8 +2441,8 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnMountInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2582,8 +2581,8 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnUpdateInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, @@ -2722,8 +2721,8 @@ if (__DEV__) { }; InvalidNestedHooksDispatcherOnRerenderInDEV = { - readCache(): ReactCache { - return readCache(); + getCacheForType(resourceType: () => T): T { + return getCacheForType(resourceType); }, readContext( context: ReactContext, diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index d2a0c7a822708..bcfb1bf55ca1a 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -23,7 +23,7 @@ import type {Flags} from './ReactFiberFlags'; import type {Lane, LanePriority, Lanes, LaneMap} from './ReactFiberLane'; import type {RootTag} from './ReactRootTags'; import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig'; -import type {Wakeable, ReactCache} from 'shared/ReactTypes'; +import type {Wakeable} from 'shared/ReactTypes'; import type {Interaction} from 'scheduler/src/Tracing'; // Unwind Circular: moved from ReactFiberHooks.old @@ -274,7 +274,7 @@ type BasicStateAction = (S => S) | S; type Dispatch
= A => void; export type Dispatcher = {| - readCache(): ReactCache, + getCacheForType(resourceType: () => T): T, readContext( context: ReactContext, observedBits: void | number | boolean, diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 8f1f0e37ddc7d..54442d9bf265e 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -40,7 +40,6 @@ import { REACT_LAZY_TYPE, REACT_MEMO_TYPE, } from 'shared/ReactSymbols'; -import type {ReactCache} from 'shared/ReactTypes'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import invariant from 'shared/invariant'; @@ -75,7 +74,7 @@ type Segment = { export type Request = { destination: Destination, bundlerConfig: BundlerConfig, - cache: ReactCache, + cache: Map, nextChunkId: number, pendingChunks: number, pingedSegments: Array, @@ -90,12 +89,6 @@ export type Request = { const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher; -function createCache(): ReactCache { - return { - resources: new Map(), - }; -} - export function createRequest( model: ReactModel, destination: Destination, @@ -105,7 +98,7 @@ export function createRequest( const request = { destination, bundlerConfig, - cache: createCache(), + cache: new Map(), nextChunkId: 0, pendingChunks: 0, pingedSegments: pingedSegments, @@ -755,7 +748,7 @@ function unsupportedHook(): void { invariant(false, 'This Hook is not supported in Server Components.'); } -let currentCache: ReactCache | null = null; +let currentCache: Map | null = null; const Dispatcher: DispatcherType = { useMemo(nextCreate: () => T): T { @@ -771,12 +764,17 @@ const Dispatcher: DispatcherType = { useTransition(): [(callback: () => void) => void, boolean] { return [() => {}, false]; }, - readCache() { + getCacheForType(resourceType: () => T): T { invariant( currentCache, 'Reading the cache is only supported while rendering.', ); - return currentCache; + if (currentCache.has(resourceType)) { + return ((currentCache.get(resourceType): any): T); + } + const entry = resourceType(); + currentCache.set(resourceType, entry); + return entry; }, readContext: (unsupportedHook: any), useContext: (unsupportedHook: any), diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 7cb564f16a211..c03ab5056f6f4 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -216,8 +216,3 @@ export interface Thenable<+R> { onReject: (error: mixed) => void | Thenable | U, ): void | Thenable; } - -export type ReactCache = {| - // TODO: a different API. - resources: Map, -|}; From 300fe29a9dd8fa24b4b837285e167e49a66d1b1c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 1 Dec 2020 19:42:45 +0000 Subject: [PATCH 06/13] Add React.unstable_getCacheForType --- packages/react-fetch/src/ReactFetchBrowser.js | 8 ++------ packages/react-fetch/src/ReactFetchNode.js | 8 ++------ packages/react/index.classic.fb.js | 1 + packages/react/index.experimental.js | 1 + packages/react/index.js | 1 + packages/react/index.modern.fb.js | 1 + packages/react/src/React.js | 2 ++ packages/react/src/ReactHooks.js | 5 +++++ 8 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/react-fetch/src/ReactFetchBrowser.js b/packages/react-fetch/src/ReactFetchBrowser.js index e6e3f369d5532..a83c8f5a6236c 100644 --- a/packages/react-fetch/src/ReactFetchBrowser.js +++ b/packages/react-fetch/src/ReactFetchBrowser.js @@ -9,7 +9,7 @@ import type {Wakeable} from 'shared/ReactTypes'; -import * as React from 'react'; +import {unstable_getCacheForType} from 'react'; const Pending = 0; const Resolved = 1; @@ -35,12 +35,8 @@ type Result = PendingResult | ResolvedResult | RejectedResult; // TODO: this is a browser-only version. Add a separate Node entry point. const nativeFetch = window.fetch; -const ReactCurrentDispatcher = - React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED - .ReactCurrentDispatcher; - function getResultMap(): Map { - return ReactCurrentDispatcher.current.getCacheForType(createResultMap); + return unstable_getCacheForType(createResultMap); } function createResultMap(): Map { diff --git a/packages/react-fetch/src/ReactFetchNode.js b/packages/react-fetch/src/ReactFetchNode.js index 7edb51c249d1e..c6260d863fc59 100644 --- a/packages/react-fetch/src/ReactFetchNode.js +++ b/packages/react-fetch/src/ReactFetchNode.js @@ -11,7 +11,7 @@ import type {Wakeable} from 'shared/ReactTypes'; import * as http from 'http'; import * as https from 'https'; -import * as React from 'react'; +import {unstable_getCacheForType} from 'react'; type FetchResponse = {| // Properties @@ -74,12 +74,8 @@ type RejectedResult = {| type Result = PendingResult | ResolvedResult | RejectedResult; -const ReactCurrentDispatcher = - React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED - .ReactCurrentDispatcher; - function getResultMap(): Map> { - return ReactCurrentDispatcher.current.getCacheForType(createResultMap); + return unstable_getCacheForType(createResultMap); } function createResultMap(): Map> { diff --git a/packages/react/index.classic.fb.js b/packages/react/index.classic.fb.js index db5d2b2a22331..04723075defa4 100644 --- a/packages/react/index.classic.fb.js +++ b/packages/react/index.classic.fb.js @@ -50,6 +50,7 @@ export { startTransition as unstable_startTransition, SuspenseList, SuspenseList as unstable_SuspenseList, + unstable_getCacheForType, // enableScopeAPI unstable_Scope, unstable_useOpaqueIdentifier, diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js index 3b2b47f1ee7cf..5de53908ae883 100644 --- a/packages/react/index.experimental.js +++ b/packages/react/index.experimental.js @@ -45,6 +45,7 @@ export { startTransition as unstable_startTransition, SuspenseList as unstable_SuspenseList, unstable_useOpaqueIdentifier, + unstable_getCacheForType, // enableDebugTracing unstable_DebugTracingMode, } from './src/React'; diff --git a/packages/react/index.js b/packages/react/index.js index 5a170667c0826..1553bdd9e9a89 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -82,4 +82,5 @@ export { unstable_createFundamental, unstable_Scope, unstable_useOpaqueIdentifier, + unstable_getCacheForType, } from './src/React'; diff --git a/packages/react/index.modern.fb.js b/packages/react/index.modern.fb.js index 8217fa158d12c..9a3bb4384ca2e 100644 --- a/packages/react/index.modern.fb.js +++ b/packages/react/index.modern.fb.js @@ -49,6 +49,7 @@ export { startTransition as unstable_startTransition, SuspenseList, SuspenseList as unstable_SuspenseList, + unstable_getCacheForType, // enableScopeAPI unstable_Scope, unstable_useOpaqueIdentifier, diff --git a/packages/react/src/React.js b/packages/react/src/React.js index c5e61fb55c67b..aae52b2750db6 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -33,6 +33,7 @@ import {lazy} from './ReactLazy'; import {forwardRef} from './ReactForwardRef'; import {memo} from './ReactMemo'; import { + getCacheForType, useCallback, useContext, useEffect, @@ -110,6 +111,7 @@ export { useDeferredValue, REACT_SUSPENSE_LIST_TYPE as SuspenseList, REACT_LEGACY_HIDDEN_TYPE as unstable_LegacyHidden, + getCacheForType as unstable_getCacheForType, // enableFundamentalAPI createFundamental as unstable_createFundamental, // enableScopeAPI diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index e1e0879c5f9f2..b1ba91029c8a2 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -36,6 +36,11 @@ function resolveDispatcher() { return dispatcher; } +export function getCacheForType(resourceType: () => T): T { + const dispatcher = resolveDispatcher(); + return dispatcher.getCacheForType(resourceType); +} + export function useContext( Context: ReactContext, unstable_observedBits: number | boolean | void, From da021be76b07025d6051e8308e602ccf9c11f031 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 2 Dec 2020 18:35:13 +0000 Subject: [PATCH 07/13] Add a feature flag --- .../src/server/ReactPartialRendererHooks.js | 6 +- .../src/ReactFiberHooks.new.js | 56 ++++++++++-------- .../src/ReactFiberHooks.old.js | 59 +++++++++++-------- .../src/ReactInternalTypes.js | 2 +- packages/react/src/ReactHooks.js | 1 + packages/shared/ReactFeatureFlags.js | 1 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../ReactFeatureFlags.test-renderer.native.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../shared/forks/ReactFeatureFlags.testing.js | 1 + .../forks/ReactFeatureFlags.testing.www.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 1 + 14 files changed, 81 insertions(+), 52 deletions(-) diff --git a/packages/react-dom/src/server/ReactPartialRendererHooks.js b/packages/react-dom/src/server/ReactPartialRendererHooks.js index 1ff3ab800ac4f..3a543aa337b6c 100644 --- a/packages/react-dom/src/server/ReactPartialRendererHooks.js +++ b/packages/react-dom/src/server/ReactPartialRendererHooks.js @@ -20,6 +20,7 @@ import type PartialRenderer from './ReactPartialRenderer'; import {validateContextBounds} from './ReactPartialRendererContext'; import invariant from 'shared/invariant'; +import {enableCache} from 'shared/ReactFeatureFlags'; import is from 'shared/objectIs'; type BasicStateAction = (S => S) | S; @@ -496,7 +497,6 @@ export function setCurrentPartialRenderer(renderer: PartialRenderer) { } export const Dispatcher: DispatcherType = { - getCacheForType, readContext, useContext, useMemo, @@ -517,3 +517,7 @@ export const Dispatcher: DispatcherType = { // Subscriptions are not setup in a server environment. useMutableSource, }; + +if (enableCache) { + Dispatcher.getCacheForType = getCacheForType; +} diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index b8a5faa2a18ac..372aa648f2acd 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -25,6 +25,7 @@ import { enableDebugTracing, enableSchedulingProfiler, enableNewReconciler, + enableCache, decoupleUpdatePriorityFromScheduler, enableUseRefAccessWarning, } from 'shared/ReactFeatureFlags'; @@ -1820,7 +1821,6 @@ function getCacheForType(resourceType: () => T): T { } export const ContextOnlyDispatcher: Dispatcher = { - getCacheForType, readContext, useCallback: throwInvalidHookError, @@ -1840,9 +1840,11 @@ export const ContextOnlyDispatcher: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; +if (enableCache) { + ContextOnlyDispatcher.getCacheForType = getCacheForType; +} const HooksDispatcherOnMount: Dispatcher = { - getCacheForType, readContext, useCallback: mountCallback, @@ -1864,7 +1866,6 @@ const HooksDispatcherOnMount: Dispatcher = { }; const HooksDispatcherOnUpdate: Dispatcher = { - getCacheForType, readContext, useCallback: updateCallback, @@ -1884,9 +1885,11 @@ const HooksDispatcherOnUpdate: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; +if (enableCache) { + HooksDispatcherOnUpdate.getCacheForType = getCacheForType; +} const HooksDispatcherOnRerender: Dispatcher = { - getCacheForType, readContext, useCallback: updateCallback, @@ -1906,6 +1909,9 @@ const HooksDispatcherOnRerender: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; +if (enableCache) { + HooksDispatcherOnRerender.getCacheForType = getCacheForType; +} let HooksDispatcherOnMountInDEV: Dispatcher | null = null; let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null; @@ -1935,9 +1941,6 @@ if (__DEV__) { }; HooksDispatcherOnMountInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2063,11 +2066,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + HooksDispatcherOnMountInDEV.getCacheForType = getCacheForType; + } HooksDispatcherOnMountWithHookTypesInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2188,11 +2191,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + HooksDispatcherOnMountWithHookTypesInDEV.getCacheForType = getCacheForType; + } HooksDispatcherOnUpdateInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2313,11 +2316,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + HooksDispatcherOnUpdateInDEV.getCacheForType = getCacheForType; + } HooksDispatcherOnRerenderInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2439,11 +2442,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + HooksDispatcherOnRerenderInDEV.getCacheForType = getCacheForType; + } InvalidNestedHooksDispatcherOnMountInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2579,11 +2582,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + InvalidNestedHooksDispatcherOnMountInDEV.getCacheForType = getCacheForType; + } InvalidNestedHooksDispatcherOnUpdateInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2719,11 +2722,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + InvalidNestedHooksDispatcherOnUpdateInDEV.getCacheForType = getCacheForType; + } InvalidNestedHooksDispatcherOnRerenderInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2860,4 +2863,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + InvalidNestedHooksDispatcherOnRerenderInDEV.getCacheForType = getCacheForType; + } } diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index 3f0017147ae76..22dbd316eb056 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -25,6 +25,7 @@ import { enableDebugTracing, enableSchedulingProfiler, enableNewReconciler, + enableCache, decoupleUpdatePriorityFromScheduler, enableUseRefAccessWarning, } from 'shared/ReactFeatureFlags'; @@ -1820,7 +1821,6 @@ function getCacheForType(resourceType: () => T): T { } export const ContextOnlyDispatcher: Dispatcher = { - getCacheForType, readContext, useCallback: throwInvalidHookError, @@ -1840,9 +1840,11 @@ export const ContextOnlyDispatcher: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; +if (enableCache) { + ContextOnlyDispatcher.getCacheForType = getCacheForType; +} const HooksDispatcherOnMount: Dispatcher = { - getCacheForType, readContext, useCallback: mountCallback, @@ -1862,9 +1864,11 @@ const HooksDispatcherOnMount: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; +if (enableCache) { + HooksDispatcherOnMount.getCacheForType = getCacheForType; +} const HooksDispatcherOnUpdate: Dispatcher = { - getCacheForType, readContext, useCallback: updateCallback, @@ -1884,9 +1888,11 @@ const HooksDispatcherOnUpdate: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; +if (enableCache) { + HooksDispatcherOnUpdate.getCacheForType = getCacheForType; +} const HooksDispatcherOnRerender: Dispatcher = { - getCacheForType, readContext, useCallback: updateCallback, @@ -1906,6 +1912,9 @@ const HooksDispatcherOnRerender: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; +if (enableCache) { + HooksDispatcherOnRerender.getCacheForType = getCacheForType; +} let HooksDispatcherOnMountInDEV: Dispatcher | null = null; let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null; @@ -1935,9 +1944,6 @@ if (__DEV__) { }; HooksDispatcherOnMountInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2063,11 +2069,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + HooksDispatcherOnMountInDEV.getCacheForType = getCacheForType; + } HooksDispatcherOnMountWithHookTypesInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2188,11 +2194,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + HooksDispatcherOnMountWithHookTypesInDEV.getCacheForType = getCacheForType; + } HooksDispatcherOnUpdateInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2313,11 +2319,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + HooksDispatcherOnUpdateInDEV.getCacheForType = getCacheForType; + } HooksDispatcherOnRerenderInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2439,11 +2445,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + HooksDispatcherOnRerenderInDEV.getCacheForType = getCacheForType; + } InvalidNestedHooksDispatcherOnMountInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2579,11 +2585,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + InvalidNestedHooksDispatcherOnMountInDEV.getCacheForType = getCacheForType; + } InvalidNestedHooksDispatcherOnUpdateInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2719,11 +2725,11 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + InvalidNestedHooksDispatcherOnUpdateInDEV.getCacheForType = getCacheForType; + } InvalidNestedHooksDispatcherOnRerenderInDEV = { - getCacheForType(resourceType: () => T): T { - return getCacheForType(resourceType); - }, readContext( context: ReactContext, observedBits: void | number | boolean, @@ -2860,4 +2866,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; + if (enableCache) { + InvalidNestedHooksDispatcherOnRerenderInDEV.getCacheForType = getCacheForType; + } } diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index bcfb1bf55ca1a..ce3f786a5186c 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -274,7 +274,7 @@ type BasicStateAction = (S => S) | S; type Dispatch = A => void; export type Dispatcher = {| - getCacheForType(resourceType: () => T): T, + getCacheForType?: (resourceType: () => T) => T, readContext( context: ReactContext, observedBits: void | number | boolean, diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index b1ba91029c8a2..1020efa74cb96 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -38,6 +38,7 @@ function resolveDispatcher() { export function getCacheForType(resourceType: () => T): T { const dispatcher = resolveDispatcher(); + // $FlowFixMe This is unstable, thus optional return dispatcher.getCacheForType(resourceType); } diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index c2c4ada6260de..e4ac713de402e 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -52,6 +52,7 @@ export const enableSelectiveHydration = __EXPERIMENTAL__; // Flight experiments export const enableLazyElements = __EXPERIMENTAL__; +export const enableCache = __EXPERIMENTAL__; // Only used in www builds. export const enableSchedulerDebugging = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 461267619d823..6dc3ec886a65f 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -21,6 +21,7 @@ export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; export const enableLazyElements = false; +export const enableCache = false; export const enableSchedulerDebugging = false; export const debugRenderPhaseSideEffectsForStrictMode = true; export const disableJavaScriptURLs = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index d3b55536eaef7..bf7cf617aa873 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -23,6 +23,7 @@ export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; export const enableLazyElements = false; +export const enableCache = false; export const disableJavaScriptURLs = false; export const disableInputAttributeSyncing = false; export const enableSchedulerDebugging = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index b20b1b1da03e0..ff863f0e7c216 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -23,6 +23,7 @@ export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; export const enableLazyElements = false; +export const enableCache = false; export const disableJavaScriptURLs = false; export const disableInputAttributeSyncing = false; export const enableSchedulerDebugging = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index b0da73bc65fb8..a2f2a8770a126 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -23,6 +23,7 @@ export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; export const enableLazyElements = false; +export const enableCache = false; export const disableJavaScriptURLs = false; export const disableInputAttributeSyncing = false; export const enableSchedulerDebugging = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index cfa4816166c0c..4504f5358036e 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -23,6 +23,7 @@ export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; export const enableLazyElements = false; +export const enableCache = false; export const enableSchedulerDebugging = false; export const disableJavaScriptURLs = false; export const disableInputAttributeSyncing = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index 757e99c6e1dc9..d72bd76be6eec 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -23,6 +23,7 @@ export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; export const enableLazyElements = false; +export const enableCache = false; export const disableJavaScriptURLs = false; export const disableInputAttributeSyncing = false; export const enableSchedulerDebugging = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index 1de76e52c3bb1..992cec53c4a50 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -23,6 +23,7 @@ export const enableSchedulerTracing = false; export const enableSuspenseServerRenderer = true; export const enableSelectiveHydration = true; export const enableLazyElements = false; +export const enableCache = false; export const disableJavaScriptURLs = true; export const disableInputAttributeSyncing = false; export const enableSchedulerDebugging = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 70d68cef060bc..7277a6368dcb9 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -58,6 +58,7 @@ export const enableSuspenseServerRenderer = true; export const enableSelectiveHydration = true; export const enableLazyElements = true; +export const enableCache = true; export const disableJavaScriptURLs = true; From 20e48bc2528518c7bdf3e9b1c08d61fba73c3e9c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 2 Dec 2020 18:37:14 +0000 Subject: [PATCH 08/13] Fix Flow --- .../src/ReactFiberHooks.new.js | 20 ++++++++--------- .../src/ReactFiberHooks.old.js | 22 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index 372aa648f2acd..f257657d2b550 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -1841,7 +1841,7 @@ export const ContextOnlyDispatcher: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - ContextOnlyDispatcher.getCacheForType = getCacheForType; + (ContextOnlyDispatcher: Dispatcher).getCacheForType = getCacheForType; } const HooksDispatcherOnMount: Dispatcher = { @@ -1886,7 +1886,7 @@ const HooksDispatcherOnUpdate: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnUpdate.getCacheForType = getCacheForType; + (HooksDispatcherOnUpdate: Dispatcher).getCacheForType = getCacheForType; } const HooksDispatcherOnRerender: Dispatcher = { @@ -1910,7 +1910,7 @@ const HooksDispatcherOnRerender: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnRerender.getCacheForType = getCacheForType; + (HooksDispatcherOnRerender: Dispatcher).getCacheForType = getCacheForType; } let HooksDispatcherOnMountInDEV: Dispatcher | null = null; @@ -2067,7 +2067,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnMountInDEV.getCacheForType = getCacheForType; + (HooksDispatcherOnMountInDEV: Dispatcher).getCacheForType = getCacheForType; } HooksDispatcherOnMountWithHookTypesInDEV = { @@ -2192,7 +2192,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnMountWithHookTypesInDEV.getCacheForType = getCacheForType; + (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).getCacheForType = getCacheForType; } HooksDispatcherOnUpdateInDEV = { @@ -2317,7 +2317,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnUpdateInDEV.getCacheForType = getCacheForType; + (HooksDispatcherOnUpdateInDEV: Dispatcher).getCacheForType = getCacheForType; } HooksDispatcherOnRerenderInDEV = { @@ -2443,7 +2443,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnRerenderInDEV.getCacheForType = getCacheForType; + (HooksDispatcherOnRerenderInDEV: Dispatcher).getCacheForType = getCacheForType; } InvalidNestedHooksDispatcherOnMountInDEV = { @@ -2583,7 +2583,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - InvalidNestedHooksDispatcherOnMountInDEV.getCacheForType = getCacheForType; + (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).getCacheForType = getCacheForType; } InvalidNestedHooksDispatcherOnUpdateInDEV = { @@ -2723,7 +2723,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - InvalidNestedHooksDispatcherOnUpdateInDEV.getCacheForType = getCacheForType; + (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).getCacheForType = getCacheForType; } InvalidNestedHooksDispatcherOnRerenderInDEV = { @@ -2864,6 +2864,6 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - InvalidNestedHooksDispatcherOnRerenderInDEV.getCacheForType = getCacheForType; + (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).getCacheForType = getCacheForType; } } diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index 22dbd316eb056..9fa15c5c0655e 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -1841,7 +1841,7 @@ export const ContextOnlyDispatcher: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - ContextOnlyDispatcher.getCacheForType = getCacheForType; + (ContextOnlyDispatcher: Dispatcher).getCacheForType = getCacheForType; } const HooksDispatcherOnMount: Dispatcher = { @@ -1865,7 +1865,7 @@ const HooksDispatcherOnMount: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnMount.getCacheForType = getCacheForType; + (HooksDispatcherOnMount: Dispatcher).getCacheForType = getCacheForType; } const HooksDispatcherOnUpdate: Dispatcher = { @@ -1889,7 +1889,7 @@ const HooksDispatcherOnUpdate: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnUpdate.getCacheForType = getCacheForType; + (HooksDispatcherOnUpdate: Dispatcher).getCacheForType = getCacheForType; } const HooksDispatcherOnRerender: Dispatcher = { @@ -1913,7 +1913,7 @@ const HooksDispatcherOnRerender: Dispatcher = { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnRerender.getCacheForType = getCacheForType; + (HooksDispatcherOnRerender: Dispatcher).getCacheForType = getCacheForType; } let HooksDispatcherOnMountInDEV: Dispatcher | null = null; @@ -2070,7 +2070,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnMountInDEV.getCacheForType = getCacheForType; + (HooksDispatcherOnMountInDEV: Dispatcher).getCacheForType = getCacheForType; } HooksDispatcherOnMountWithHookTypesInDEV = { @@ -2195,7 +2195,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnMountWithHookTypesInDEV.getCacheForType = getCacheForType; + (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).getCacheForType = getCacheForType; } HooksDispatcherOnUpdateInDEV = { @@ -2320,7 +2320,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnUpdateInDEV.getCacheForType = getCacheForType; + (HooksDispatcherOnUpdateInDEV: Dispatcher).getCacheForType = getCacheForType; } HooksDispatcherOnRerenderInDEV = { @@ -2446,7 +2446,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - HooksDispatcherOnRerenderInDEV.getCacheForType = getCacheForType; + (HooksDispatcherOnRerenderInDEV: Dispatcher).getCacheForType = getCacheForType; } InvalidNestedHooksDispatcherOnMountInDEV = { @@ -2586,7 +2586,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - InvalidNestedHooksDispatcherOnMountInDEV.getCacheForType = getCacheForType; + (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).getCacheForType = getCacheForType; } InvalidNestedHooksDispatcherOnUpdateInDEV = { @@ -2726,7 +2726,7 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - InvalidNestedHooksDispatcherOnUpdateInDEV.getCacheForType = getCacheForType; + (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).getCacheForType = getCacheForType; } InvalidNestedHooksDispatcherOnRerenderInDEV = { @@ -2867,6 +2867,6 @@ if (__DEV__) { unstable_isNewReconciler: enableNewReconciler, }; if (enableCache) { - InvalidNestedHooksDispatcherOnRerenderInDEV.getCacheForType = getCacheForType; + (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).getCacheForType = getCacheForType; } } From c819023afef0a5996a6995407c5f7b9ecb758b40 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 3 Dec 2020 01:49:20 +0000 Subject: [PATCH 09/13] Add react-suspense-test-utils and port tests --- .../src/__tests__/ReactFetchNode-test.js | 139 ++++-------------- packages/react-suspense-test-utils/README.md | 12 ++ packages/react-suspense-test-utils/index.js | 10 ++ .../react-suspense-test-utils/npm/index.js | 3 + .../react-suspense-test-utils/package.json | 20 +++ .../src/ReactSuspenseTestUtils.js | 64 ++++++++ scripts/error-codes/codes.json | 3 +- scripts/rollup/bundles.js | 9 ++ 8 files changed, 146 insertions(+), 114 deletions(-) create mode 100644 packages/react-suspense-test-utils/README.md create mode 100644 packages/react-suspense-test-utils/index.js create mode 100644 packages/react-suspense-test-utils/npm/index.js create mode 100644 packages/react-suspense-test-utils/package.json create mode 100644 packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js diff --git a/packages/react-fetch/src/__tests__/ReactFetchNode-test.js b/packages/react-fetch/src/__tests__/ReactFetchNode-test.js index 537d4a993b67e..2b31546a221c3 100644 --- a/packages/react-fetch/src/__tests__/ReactFetchNode-test.js +++ b/packages/react-fetch/src/__tests__/ReactFetchNode-test.js @@ -9,23 +9,10 @@ 'use strict'; -// Polyfills for test environment -global.ReadableStream = require('@mattiasbuelens/web-streams-polyfill/ponyfill/es6').ReadableStream; -global.TextDecoder = require('util').TextDecoder; - -// Don't wait before processing work on the server. -// TODO: we can replace this with FlightServer.act(). -global.setImmediate = cb => cb(); - describe('ReactFetchNode', () => { - let React; - let ReactDOM; - let ReactTransportDOMServer; - let ReactTransportDOMClient; - let Stream; - let act; let http; let fetch; + let waitForSuspense; let server; let serverEndpoint; let serverImpl; @@ -33,14 +20,9 @@ describe('ReactFetchNode', () => { beforeEach(done => { jest.resetModules(); - Stream = require('stream'); - React = require('react'); - ReactDOM = require('react-dom'); - ReactTransportDOMServer = require('react-transport-dom-webpack/server'); - ReactTransportDOMClient = require('react-transport-dom-webpack'); - act = require('react-dom/test-utils').act; fetch = require('react-fetch').fetch; http = require('http'); + waitForSuspense = require('react-suspense-test-utils').waitForSuspense; server = http.createServer((req, res) => { serverImpl(req, res); @@ -57,61 +39,16 @@ describe('ReactFetchNode', () => { server = null; }); - function getTestStream() { - const writable = new Stream.PassThrough(); - const readable = new ReadableStream({ - start(controller) { - writable.on('data', chunk => { - controller.enqueue(chunk); - }); - writable.on('end', () => { - controller.close(); - }); - }, - }); - return { - writable, - readable, - }; - } - - async function getServerOutput(serverTree) { - const {writable, readable} = getTestStream(); - ReactTransportDOMServer.pipeToNodeWritable(serverTree, writable, {}); - const response = ReactTransportDOMClient.createFromReadableStream(readable); - - const container = document.createElement('div'); - const root = ReactDOM.unstable_createRoot(container); - - function Client() { - return response.readRoot(); - } - - await act(async () => { - root.render( - - - , - ); - }); - while (container.innerHTML === 'loading...') { - await act(async () => {}); - } - return container.innerHTML; - } - // @gate experimental it('can fetch text from a server component', async () => { serverImpl = (req, res) => { res.write('mango'); res.end(); }; - function App() { - const text = fetch(serverEndpoint).text(); - return
{text.toUpperCase()}
; - } - const output = await getServerOutput(); - expect(output).toEqual('
MANGO
'); + const text = await waitForSuspense(() => { + return fetch(serverEndpoint).text(); + }); + expect(text).toEqual('mango'); }); // @gate experimental @@ -120,12 +57,10 @@ describe('ReactFetchNode', () => { res.write(JSON.stringify({name: 'Sema'})); res.end(); }; - function App() { - const json = fetch(serverEndpoint).json(); - return
{json.name.toUpperCase()}
; - } - const output = await getServerOutput(); - expect(output).toEqual('
SEMA
'); + const json = await waitForSuspense(() => { + return fetch(serverEndpoint).json(); + }); + expect(json).toEqual({name: 'Sema'}); }); // @gate experimental @@ -134,16 +69,14 @@ describe('ReactFetchNode', () => { res.write(JSON.stringify({name: 'Sema'})); res.end(); }; - function App() { - const response = fetch(serverEndpoint); - return ( -
- {response.status} {response.statusText} {'' + response.ok} -
- ); - } - const output = await getServerOutput(); - expect(output).toEqual('
200 OK true
'); + const response = await waitForSuspense(() => { + return fetch(serverEndpoint); + }); + expect(response).toMatchObject({ + status: 200, + statusText: 'OK', + ok: true, + }); }); // @gate experimental @@ -162,33 +95,13 @@ describe('ReactFetchNode', () => { } res.end(); }; - function Banana() { - return {fetch(serverEndpoint + 'banana').text()}; - } - function Mango() { - return {fetch(serverEndpoint + 'mango').text()}; - } - function Orange() { - return {fetch(serverEndpoint + 'orange').text()}; - } - function App() { - return ( -
- - - - -
- ); - } - const output = await getServerOutput(); - expect(output).toEqual( - '
' + - 'banana' + - 'mango' + - 'orange' + - 'mango' + - '
', - ); + const outputs = await waitForSuspense(() => { + return [ + fetch(serverEndpoint + 'banana').text(), + fetch(serverEndpoint + 'mango').text(), + fetch(serverEndpoint + 'orange').text(), + ]; + }); + expect(outputs).toMatchObject(['banana', 'mango', 'orange']); }); }); diff --git a/packages/react-suspense-test-utils/README.md b/packages/react-suspense-test-utils/README.md new file mode 100644 index 0000000000000..77630cd7507bd --- /dev/null +++ b/packages/react-suspense-test-utils/README.md @@ -0,0 +1,12 @@ +# react-suspense-test-utils + +This package is meant to be used alongside yet-to-be-released, experimental React features. It's unlikely to be useful in any other context. + +**Do not use in a real application.** We're publishing this early for +demonstration purposes. + +**Use it at your own risk.** + +# No, Really, It Is Unstable + +The API ~~may~~ will change wildly between versions. diff --git a/packages/react-suspense-test-utils/index.js b/packages/react-suspense-test-utils/index.js new file mode 100644 index 0000000000000..d0e5204abab23 --- /dev/null +++ b/packages/react-suspense-test-utils/index.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from './src/ReactSuspenseTestUtils'; diff --git a/packages/react-suspense-test-utils/npm/index.js b/packages/react-suspense-test-utils/npm/index.js new file mode 100644 index 0000000000000..dc9c621a5b77b --- /dev/null +++ b/packages/react-suspense-test-utils/npm/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./cjs/react-suspense-test-utils.js'); diff --git a/packages/react-suspense-test-utils/package.json b/packages/react-suspense-test-utils/package.json new file mode 100644 index 0000000000000..b23c3fc2e31c9 --- /dev/null +++ b/packages/react-suspense-test-utils/package.json @@ -0,0 +1,20 @@ +{ + "name": "react-suspense-test-utils", + "version": "0.1.0", + "private": true, + "repository": { + "type" : "git", + "url" : "https://github.com/facebook/react.git", + "directory": "packages/react-suspense-test-utils" + }, + "license": "MIT", + "files": [ + "LICENSE", + "README.md", + "index.js", + "cjs/" + ], + "peerDependencies": { + "react": "^17.0.0" + } +} diff --git a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js new file mode 100644 index 0000000000000..5dfe29bc72137 --- /dev/null +++ b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js @@ -0,0 +1,64 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes'; +import ReactSharedInternals from 'shared/ReactSharedInternals'; +import invariant from 'shared/invariant'; + +const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher; + +function unsupported() { + invariant(false, 'This feature is not supported by ReactSuspenseTestUtils.'); +} + +export async function waitForSuspense(fn: () => T): Promise { + const cache = new Map(); + const testDispatcher: Dispatcher = { + getCacheForType(resourceType: () => R): R { + if (cache.has(resourceType)) { + return ((cache.get(resourceType): any): R); + } + const entry = resourceType(); + cache.set(resourceType, entry); + return entry; + }, + readContext: unsupported, + useContext: unsupported, + useMemo: unsupported, + useReducer: unsupported, + useRef: unsupported, + useState: unsupported, + useLayoutEffect: unsupported, + useCallback: unsupported, + useImperativeHandle: unsupported, + useEffect: unsupported, + useDebugValue: unsupported, + useDeferredValue: unsupported, + useTransition: unsupported, + useOpaqueIdentifier: unsupported, + useMutableSource: unsupported, + }; + while (true) { + const prevDispatcher = ReactCurrentDispatcher.current; + ReactCurrentDispatcher.current = testDispatcher; + try { + return fn(); + } catch (promise) { + if (typeof promise.then === 'function') { + await promise; + } else { + throw promise; + } + } finally { + ReactCurrentDispatcher.current = prevDispatcher; + } + } + // eslint-disable-next-line no-unreachable + return (undefined: any); +} diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index c1a61c34cfaa5..444e3ff809229 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -368,5 +368,6 @@ "377": "BigInt (%s) is not yet supported in client component props. Remove %s from this object or use a plain number instead: %s", "378": "Type %s is not supported in client component props. Remove %s from this object, or avoid the entire object: %s", "379": "Refs cannot be used in server components, nor passed to client components.", - "380": "Reading the cache is only supported while rendering." + "380": "Reading the cache is only supported while rendering.", + "381": "This feature is not supported by ReactSuspenseTestUtils." } diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index c8244e8bb9a75..db8a8b8002cfb 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -357,6 +357,15 @@ const bundles = [ ], }, + /******* React Suspense Test Utils *******/ + { + bundleTypes: [NODE_ES2015], + moduleType: RENDERER_UTILS, + entry: 'react-suspense-test-utils', + global: 'ReactSuspenseTestUtils', + externals: ['react'], + }, + /******* React ART *******/ { bundleTypes: [ From 0e3d16d677b998cfd9cc5afef932aef09358b95f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 3 Dec 2020 01:54:07 +0000 Subject: [PATCH 10/13] Remove extra Map lookup --- packages/react-server/src/ReactFlightServer.js | 9 +++++---- .../src/ReactSuspenseTestUtils.js | 11 ++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 54442d9bf265e..e4f307998ba19 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -769,11 +769,12 @@ const Dispatcher: DispatcherType = { currentCache, 'Reading the cache is only supported while rendering.', ); - if (currentCache.has(resourceType)) { - return ((currentCache.get(resourceType): any): T); + let entry: T | void = (currentCache.get(resourceType): any); + if (entry === undefined) { + entry = resourceType(); + // TODO: Warn if undefined? + currentCache.set(resourceType, entry); } - const entry = resourceType(); - currentCache.set(resourceType, entry); return entry; }, readContext: (unsupportedHook: any), diff --git a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js index 5dfe29bc72137..98c0132c01941 100644 --- a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js +++ b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js @@ -18,14 +18,15 @@ function unsupported() { } export async function waitForSuspense(fn: () => T): Promise { - const cache = new Map(); + const cache: Map = new Map(); const testDispatcher: Dispatcher = { getCacheForType(resourceType: () => R): R { - if (cache.has(resourceType)) { - return ((cache.get(resourceType): any): R); + let entry: R | void = (cache.get(resourceType): any); + if (entry === undefined) { + entry = resourceType(); + // TODO: Warn if undefined? + cache.set(resourceType, entry); } - const entry = resourceType(); - cache.set(resourceType, entry); return entry; }, readContext: unsupported, From 5186e0108d845e7e88d73866f4fc1ec09c25ac57 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 3 Dec 2020 02:36:37 +0000 Subject: [PATCH 11/13] Unroll async/await because build system --- .../src/ReactSuspenseTestUtils.js | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js index 98c0132c01941..e6daf6ec6c2e3 100644 --- a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js +++ b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js @@ -17,7 +17,7 @@ function unsupported() { invariant(false, 'This feature is not supported by ReactSuspenseTestUtils.'); } -export async function waitForSuspense(fn: () => T): Promise { +export function waitForSuspense(fn: () => T): Promise { const cache: Map = new Map(); const testDispatcher: Dispatcher = { getCacheForType(resourceType: () => R): R { @@ -45,21 +45,24 @@ export async function waitForSuspense(fn: () => T): Promise { useOpaqueIdentifier: unsupported, useMutableSource: unsupported, }; - while (true) { - const prevDispatcher = ReactCurrentDispatcher.current; - ReactCurrentDispatcher.current = testDispatcher; - try { - return fn(); - } catch (promise) { - if (typeof promise.then === 'function') { - await promise; - } else { - throw promise; + // Not using async/await because we don't compile it. + return new Promise((resolve, reject) => { + function retry() { + const prevDispatcher = ReactCurrentDispatcher.current; + ReactCurrentDispatcher.current = testDispatcher; + try { + const result = fn(); + resolve(result); + } catch (thrownValue) { + if (typeof thrownValue.then === 'function') { + thrownValue.then(retry, reject); + } else { + reject(thrownValue); + } + } finally { + ReactCurrentDispatcher.current = prevDispatcher; } - } finally { - ReactCurrentDispatcher.current = prevDispatcher; } - } - // eslint-disable-next-line no-unreachable - return (undefined: any); + retry(); + }); } From a92435e165d7eda5629f340c5cc72c617fd198fb Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 3 Dec 2020 02:53:38 +0000 Subject: [PATCH 12/13] Add some error coverage and retry --- .../src/__tests__/ReactFetchNode-test.js | 14 ++++++++++++++ .../src/ReactSuspenseTestUtils.js | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/react-fetch/src/__tests__/ReactFetchNode-test.js b/packages/react-fetch/src/__tests__/ReactFetchNode-test.js index 2b31546a221c3..6ad5fdc9c434f 100644 --- a/packages/react-fetch/src/__tests__/ReactFetchNode-test.js +++ b/packages/react-fetch/src/__tests__/ReactFetchNode-test.js @@ -104,4 +104,18 @@ describe('ReactFetchNode', () => { }); expect(outputs).toMatchObject(['banana', 'mango', 'orange']); }); + + // @gate experimental + it('can produce an error', async () => { + serverImpl = (req, res) => {}; + + expect.assertions(1); + try { + await waitForSuspense(() => { + return fetch('BOOM'); + }); + } catch (err) { + expect(err.message).toEqual('Invalid URL: BOOM'); + } + }); }); diff --git a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js index e6daf6ec6c2e3..a58c6cae824c0 100644 --- a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js +++ b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js @@ -55,7 +55,7 @@ export function waitForSuspense(fn: () => T): Promise { resolve(result); } catch (thrownValue) { if (typeof thrownValue.then === 'function') { - thrownValue.then(retry, reject); + thrownValue.then(retry, retry); } else { reject(thrownValue); } From a16d2ab1733eede4513db0d3293cb17c60cb9c86 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 3 Dec 2020 03:06:01 +0000 Subject: [PATCH 13/13] Add unstable_getCacheForType to Flight entry --- packages/react/unstable-index.server.experimental.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react/unstable-index.server.experimental.js b/packages/react/unstable-index.server.experimental.js index 479094f46de6f..890066957e383 100644 --- a/packages/react/unstable-index.server.experimental.js +++ b/packages/react/unstable-index.server.experimental.js @@ -32,6 +32,7 @@ export { useDeferredValue as unstable_useDeferredValue, SuspenseList as unstable_SuspenseList, unstable_useOpaqueIdentifier, + unstable_getCacheForType, // enableDebugTracing unstable_DebugTracingMode, } from './src/React';