Delete Partial Renderer SSR implementation (#24868)

This removes the old server rendering implementation (the "Partial Renderer").
It was replaced in React 18 with a new streaming implementation (Fizz).

We hadn't removed it from the codebase yet because Facebook hadn't finished
rolling out Fizz in production; it's been behind a feature flag while we run
performance tests and migrate our internal infrastructure.

The diff to land Fizz will land imminently, and once it does, we can merge
this commit.
This commit is contained in:
Andrew Clark 2022-07-07 16:57:42 -04:00 committed by GitHub
parent c3b18571db
commit 95e22ff528
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 11 additions and 2518 deletions

View File

@ -11,7 +11,6 @@ let React;
let ReactDOMClient;
let ReactDOMServer;
let act;
let usingPartialRenderer;
const util = require('util');
const realConsoleError = console.error;
@ -26,8 +25,6 @@ describe('ReactDOMServerHydration', () => {
ReactDOMServer = require('react-dom/server');
act = require('react-dom/test-utils').act;
usingPartialRenderer = global.__WWW__ && !__EXPERIMENTAL__;
console.error = jest.fn();
container = document.createElement('div');
document.body.appendChild(container);
@ -731,15 +728,13 @@ describe('ReactDOMServerHydration', () => {
);
}
// @TODO FB bundles use a different renderer that does not serialize errors to the client
const mismatchEl = usingPartialRenderer ? '<p>' : '<template>';
// @TODO changes made to sending Fizz errors to client led to the insertion of templates in client rendered
// suspense boundaries. This leaks in this test becuase the client rendered suspense boundary appears like
// unhydrated tail nodes and this template is the first match. When we add special case handling for client
// rendered suspense boundaries this test will likely change again
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
Array [
"Warning: Did not expect server HTML to contain a ${mismatchEl} in <div>.
"Warning: Did not expect server HTML to contain a <template> in <div>.
in div (at **)
in Mismatch (at **)",
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
@ -819,21 +814,12 @@ describe('ReactDOMServerHydration', () => {
</div>
);
}
// We gate this assertion becuase fb-classic uses PartialRenderer for renderToString and it does not
// serialize server errors and send to client
if (usingPartialRenderer) {
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
Array [
"Caught [The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.]",
]
`);
} else {
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
Array [
"Caught [The server did not finish this Suspense boundary: The server used \\"renderToString\\" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to \\"renderToPipeableStream\\" which supports Suspense on the server]",
]
`);
}
});
// @gate __DEV__
@ -854,21 +840,12 @@ describe('ReactDOMServerHydration', () => {
</div>
);
}
// We gate this assertion becuase fb-classic uses PartialRenderer for renderToString and it does not
// serialize server errors and send to client
if (usingPartialRenderer) {
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
Array [
"Caught [The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.]",
]
`);
} else {
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
Array [
"Caught [The server did not finish this Suspense boundary: The server used \\"renderToString\\" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to \\"renderToPipeableStream\\" which supports Suspense on the server]",
]
`);
}
});
});

View File

@ -19,7 +19,6 @@ let Suspense;
let SuspenseList;
let act;
let IdleEventPriority;
let usingPartialRenderer;
function normalizeCodeLocInfo(strOrErr) {
if (strOrErr && strOrErr.replace) {
@ -111,8 +110,6 @@ describe('ReactDOMServerPartialHydration', () => {
SuspenseList = React.SuspenseList;
}
usingPartialRenderer = global.__WWW__ && !__EXPERIMENTAL__;
IdleEventPriority = require('react-reconciler/constants').IdleEventPriority;
});
@ -1671,8 +1668,7 @@ describe('ReactDOMServerPartialHydration', () => {
Scheduler.unstable_yieldValue(error.message);
},
});
// we exclude fb bundles with partial renderer
if (__DEV__ && !usingPartialRenderer) {
if (__DEV__) {
expect(Scheduler).toFlushAndYield([
'The server did not finish this Suspense boundary: The server used' +
' "renderToString" which does not support Suspense. If you intended' +
@ -1745,8 +1741,7 @@ describe('ReactDOMServerPartialHydration', () => {
Scheduler.unstable_yieldValue(error.message);
},
});
// we exclude fb bundles with partial renderer
if (__DEV__ && !usingPartialRenderer) {
if (__DEV__) {
expect(Scheduler).toFlushAndYield([
'The server did not finish this Suspense boundary: The server used' +
' "renderToString" which does not support Suspense. If you intended' +
@ -1824,8 +1819,7 @@ describe('ReactDOMServerPartialHydration', () => {
Scheduler.unstable_yieldValue(error.message);
},
});
// we exclude fb bundles with partial renderer
if (__DEV__ && !usingPartialRenderer) {
if (__DEV__) {
expect(Scheduler).toFlushAndYield([
'The server did not finish this Suspense boundary: The server used' +
' "renderToString" which does not support Suspense. If you intended' +
@ -2154,8 +2148,7 @@ describe('ReactDOMServerPartialHydration', () => {
});
suspend = true;
// we exclude fb bundles with partial renderer
if (__DEV__ && !usingPartialRenderer) {
if (__DEV__) {
expect(Scheduler).toFlushAndYield([
'The server did not finish this Suspense boundary: The server used' +
' "renderToString" which does not support Suspense. If you intended' +
@ -2229,8 +2222,7 @@ describe('ReactDOMServerPartialHydration', () => {
Scheduler.unstable_yieldValue(error.message);
},
});
// we exclude fb bundles with partial renderer
if (__DEV__ && !usingPartialRenderer) {
if (__DEV__) {
expect(Scheduler).toFlushAndYield([
'The server did not finish this Suspense boundary: The server used' +
' "renderToString" which does not support Suspense. If you intended' +

View File

@ -14,7 +14,6 @@ let React;
let ReactDOMServer;
let PropTypes;
let ReactCurrentDispatcher;
let useingPartialRenderer;
describe('ReactDOMServer', () => {
beforeEach(() => {
@ -25,8 +24,6 @@ describe('ReactDOMServer', () => {
ReactCurrentDispatcher =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.ReactCurrentDispatcher;
useingPartialRenderer = global.__WWW__ && !__EXPERIMENTAL__;
});
describe('renderToString', () => {
@ -576,11 +573,7 @@ describe('ReactDOMServer', () => {
<Suspender />
</React.Suspense>,
);
if (useingPartialRenderer) {
expect(response).toEqual('<!--$!-->fallback<!--/$-->');
} else {
expect(response).toEqual('fallback');
}
expect(response).toEqual('fallback');
});
});

View File

@ -603,7 +603,6 @@ describe('ReactDOMServerHydration', () => {
expect(customElement.obj).toBe(undefined);
});
// @gate experimental || !www || !__DEV__
it('refers users to apis that support Suspense when something suspends', () => {
const theInfinitePromise = new Promise(() => {});
function InfiniteSuspend() {
@ -649,7 +648,6 @@ describe('ReactDOMServerHydration', () => {
}
});
// @gate experimental || !www || !__DEV__
it('refers users to apis that support Suspense when something suspends (browser)', () => {
const theInfinitePromise = new Promise(() => {});
function InfiniteSuspend() {

View File

@ -1,16 +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 {
renderToString,
renderToStaticMarkup,
renderToNodeStream,
renderToStaticNodeStream,
version,
} from './ReactDOMServerLegacyPartialRendererBrowser';

View File

@ -1,19 +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 {
renderToString,
renderToStaticMarkup,
version,
} from './ReactDOMServerLegacyPartialRendererBrowser';
export {
renderToNodeStream,
renderToStaticNodeStream,
} from './ReactDOMLegacyServerNodeStream';

View File

@ -1,32 +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.
*/
import ReactVersion from 'shared/ReactVersion';
import {renderToString, renderToStaticMarkup} from './ReactDOMStringRenderer';
function renderToNodeStream() {
throw new Error(
'ReactDOMServer.renderToNodeStream(): The streaming API is not available ' +
'in the browser. Use ReactDOMServer.renderToString() instead.',
);
}
function renderToStaticNodeStream() {
throw new Error(
'ReactDOMServer.renderToStaticNodeStream(): The streaming API is not available ' +
'in the browser. Use ReactDOMServer.renderToStaticMarkup() instead.',
);
}
export {
renderToString,
renderToStaticMarkup,
renderToNodeStream,
renderToStaticNodeStream,
ReactVersion as version,
};

View File

@ -1,39 +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.
*/
import type {ServerOptions} from './ReactPartialRenderer';
import ReactPartialRenderer from './ReactPartialRenderer';
/**
* Render a ReactElement to its initial HTML. This should only be used on the
* server.
* See https://reactjs.org/docs/react-dom-server.html#rendertostring
*/
export function renderToString(element, options?: ServerOptions) {
const renderer = new ReactPartialRenderer(element, false, options);
try {
const markup = renderer.read(Infinity);
return markup;
} finally {
renderer.destroy();
}
}
/**
* Similar to renderToString, except this doesn't create extra DOM attributes
* such as data-react-id that React uses internally.
* See https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup
*/
export function renderToStaticMarkup(element, options?: ServerOptions) {
const renderer = new ReactPartialRenderer(element, true, options);
try {
const markup = renderer.read(Infinity);
return markup;
} finally {
renderer.destroy();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,160 +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 {ThreadID} from './ReactThreadIDAllocator';
import type {ReactContext} from 'shared/ReactTypes';
import {disableLegacyContext} from 'shared/ReactFeatureFlags';
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import checkPropTypes from 'shared/checkPropTypes';
let didWarnAboutInvalidateContextType;
if (__DEV__) {
didWarnAboutInvalidateContextType = new Set();
}
export const emptyObject = {};
if (__DEV__) {
Object.freeze(emptyObject);
}
function maskContext(type, context) {
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyObject;
}
const maskedContext = {};
for (const contextName in contextTypes) {
maskedContext[contextName] = context[contextName];
}
return maskedContext;
}
function checkContextTypes(typeSpecs, values, location: string) {
if (__DEV__) {
checkPropTypes(typeSpecs, values, location, 'Component');
}
}
export function validateContextBounds(
context: ReactContext<any>,
threadID: ThreadID,
) {
// If we don't have enough slots in this context to store this threadID,
// fill it in without leaving any holes to ensure that the VM optimizes
// this as non-holey index properties.
// (Note: If `react` package is < 16.6, _threadCount is undefined.)
for (let i = context._threadCount | 0; i <= threadID; i++) {
// We assume that this is the same as the defaultValue which might not be
// true if we're rendering inside a secondary renderer but they are
// secondary because these use cases are very rare.
context[i] = context._currentValue2;
context._threadCount = i + 1;
}
}
export function processContext(
type: Function,
context: Object,
threadID: ThreadID,
isClass: boolean,
) {
if (isClass) {
const contextType = type.contextType;
if (__DEV__) {
if ('contextType' in (type: any)) {
const isValid =
// Allow null for conditional declaration
contextType === null ||
(contextType !== undefined &&
contextType.$$typeof === REACT_CONTEXT_TYPE &&
contextType._context === undefined); // Not a <Context.Consumer>
if (!isValid && !didWarnAboutInvalidateContextType.has(type)) {
didWarnAboutInvalidateContextType.add(type);
let addendum = '';
if (contextType === undefined) {
addendum =
' However, it is set to undefined. ' +
'This can be caused by a typo or by mixing up named and default imports. ' +
'This can also happen due to a circular dependency, so ' +
'try moving the createContext() call to a separate file.';
} else if (typeof contextType !== 'object') {
addendum = ' However, it is set to a ' + typeof contextType + '.';
} else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
addendum =
' Did you accidentally pass the Context.Provider instead?';
} else if (contextType._context !== undefined) {
// <Context.Consumer>
addendum =
' Did you accidentally pass the Context.Consumer instead?';
} else {
addendum =
' However, it is set to an object with keys {' +
Object.keys(contextType).join(', ') +
'}.';
}
console.error(
'%s defines an invalid contextType. ' +
'contextType should point to the Context object returned by React.createContext().%s',
getComponentNameFromType(type) || 'Component',
addendum,
);
}
}
}
if (typeof contextType === 'object' && contextType !== null) {
validateContextBounds(contextType, threadID);
return contextType[threadID];
}
if (disableLegacyContext) {
if (__DEV__) {
if (type.contextTypes) {
console.error(
'%s uses the legacy contextTypes API which is no longer supported. ' +
'Use React.createContext() with static contextType instead.',
getComponentNameFromType(type) || 'Unknown',
);
}
}
return emptyObject;
} else {
const maskedContext = maskContext(type, context);
if (__DEV__) {
if (type.contextTypes) {
checkContextTypes(type.contextTypes, maskedContext, 'context');
}
}
return maskedContext;
}
} else {
if (disableLegacyContext) {
if (__DEV__) {
if (type.contextTypes) {
console.error(
'%s uses the legacy contextTypes API which is no longer supported. ' +
'Use React.createContext() with React.useContext() instead.',
getComponentNameFromType(type) || 'Unknown',
);
}
}
return undefined;
} else {
const maskedContext = maskContext(type, context);
if (__DEV__) {
if (type.contextTypes) {
checkContextTypes(type.contextTypes, maskedContext, 'context');
}
}
return maskedContext;
}
}
}

View File

@ -1,555 +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 {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
import type {
MutableSource,
MutableSourceGetSnapshotFn,
MutableSourceSubscribeFn,
ReactContext,
} from 'shared/ReactTypes';
import type PartialRenderer from './ReactPartialRenderer';
import {validateContextBounds} from './ReactPartialRendererContext';
import {enableCache} from 'shared/ReactFeatureFlags';
import is from 'shared/objectIs';
type BasicStateAction<S> = (S => S) | S;
type Dispatch<A> = A => void;
type Update<A> = {|
action: A,
next: Update<A> | null,
|};
type UpdateQueue<A> = {|
last: Update<A> | null,
dispatch: any,
|};
type Hook = {|
memoizedState: any,
queue: UpdateQueue<any> | null,
next: Hook | null,
|};
let currentlyRenderingComponent: Object | null = null;
let firstWorkInProgressHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
// Whether the work-in-progress hook is a re-rendered hook
let isReRender: boolean = false;
// Whether an update was scheduled during the currently executing render pass.
let didScheduleRenderPhaseUpdate: boolean = false;
// Lazily created map of render-phase updates
let renderPhaseUpdates: Map<UpdateQueue<any>, Update<any>> | null = null;
// Counter to prevent infinite loops.
let numberOfReRenders: number = 0;
const RE_RENDER_LIMIT = 25;
let isInHookUserCodeInDev = false;
// In DEV, this is the name of the currently executing primitive hook
let currentHookNameInDev: ?string;
function resolveCurrentlyRenderingComponent(): Object {
if (currentlyRenderingComponent === null) {
throw new Error(
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
' one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
);
}
if (__DEV__) {
if (isInHookUserCodeInDev) {
console.error(
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
'You can only call Hooks at the top level of your React function. ' +
'For more information, see ' +
'https://reactjs.org/link/rules-of-hooks',
);
}
}
return currentlyRenderingComponent;
}
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
) {
if (prevDeps === null) {
if (__DEV__) {
console.error(
'%s received a final argument during this render, but not during ' +
'the previous render. Even though the final argument is optional, ' +
'its type cannot change between renders.',
currentHookNameInDev,
);
}
return false;
}
if (__DEV__) {
// Don't bother comparing lengths in prod because these arrays should be
// passed inline.
if (nextDeps.length !== prevDeps.length) {
console.error(
'The final argument passed to %s changed size between renders. The ' +
'order and size of this array must remain constant.\n\n' +
'Previous: %s\n' +
'Incoming: %s',
currentHookNameInDev,
`[${nextDeps.join(', ')}]`,
`[${prevDeps.join(', ')}]`,
);
}
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
function createHook(): Hook {
if (numberOfReRenders > 0) {
throw new Error('Rendered more hooks than during the previous render');
}
return {
memoizedState: null,
queue: null,
next: null,
};
}
function createWorkInProgressHook(): Hook {
if (workInProgressHook === null) {
// This is the first hook in the list
if (firstWorkInProgressHook === null) {
isReRender = false;
firstWorkInProgressHook = workInProgressHook = createHook();
} else {
// There's already a work-in-progress. Reuse it.
isReRender = true;
workInProgressHook = firstWorkInProgressHook;
}
} else {
if (workInProgressHook.next === null) {
isReRender = false;
// Append to the end of the list
workInProgressHook = workInProgressHook.next = createHook();
} else {
// There's already a work-in-progress. Reuse it.
isReRender = true;
workInProgressHook = workInProgressHook.next;
}
}
return workInProgressHook;
}
export function prepareToUseHooks(componentIdentity: Object): void {
currentlyRenderingComponent = componentIdentity;
if (__DEV__) {
isInHookUserCodeInDev = false;
}
// The following should have already been reset
// didScheduleRenderPhaseUpdate = false;
// firstWorkInProgressHook = null;
// numberOfReRenders = 0;
// renderPhaseUpdates = null;
// workInProgressHook = null;
}
export function finishHooks(
Component: any,
props: any,
children: any,
refOrContext: any,
): any {
// This must be called after every function component to prevent hooks from
// being used in classes.
while (didScheduleRenderPhaseUpdate) {
// Updates were scheduled during the render phase. They are stored in
// the `renderPhaseUpdates` map. Call the component again, reusing the
// work-in-progress hooks and applying the additional updates on top. Keep
// restarting until no more updates are scheduled.
didScheduleRenderPhaseUpdate = false;
numberOfReRenders += 1;
// Start over from the beginning of the list
workInProgressHook = null;
children = Component(props, refOrContext);
}
resetHooksState();
return children;
}
// Reset the internal hooks state if an error occurs while rendering a component
export function resetHooksState(): void {
if (__DEV__) {
isInHookUserCodeInDev = false;
}
currentlyRenderingComponent = null;
didScheduleRenderPhaseUpdate = false;
firstWorkInProgressHook = null;
numberOfReRenders = 0;
renderPhaseUpdates = null;
workInProgressHook = null;
}
function getCacheSignal() {
throw new Error('Not implemented.');
}
function getCacheForType<T>(resourceType: () => T): T {
throw new Error('Not implemented.');
}
function readContext<T>(context: ReactContext<T>): T {
const threadID = currentPartialRenderer.threadID;
validateContextBounds(context, threadID);
if (__DEV__) {
if (isInHookUserCodeInDev) {
console.error(
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
}
}
return context[threadID];
}
function useContext<T>(context: ReactContext<T>): T {
if (__DEV__) {
currentHookNameInDev = 'useContext';
}
resolveCurrentlyRenderingComponent();
const threadID = currentPartialRenderer.threadID;
validateContextBounds(context, threadID);
return context[threadID];
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
}
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
if (__DEV__) {
currentHookNameInDev = 'useState';
}
return useReducer(
basicStateReducer,
// useReducer has a special case to support lazy useState initializers
(initialState: any),
);
}
export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
if (__DEV__) {
if (reducer !== basicStateReducer) {
currentHookNameInDev = 'useReducer';
}
}
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
if (isReRender) {
// This is a re-render. Apply the new render phase updates to the previous
// current hook.
const queue: UpdateQueue<A> = (workInProgressHook.queue: any);
const dispatch: Dispatch<A> = (queue.dispatch: any);
if (renderPhaseUpdates !== null) {
// Render phase updates are stored in a map of queue -> linked list
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
let newState = workInProgressHook.memoizedState;
let update = firstRenderPhaseUpdate;
do {
// Process this render phase update. We don't have to check the
// priority because it will always be the same as the current
// render's.
const action = update.action;
if (__DEV__) {
isInHookUserCodeInDev = true;
}
newState = reducer(newState, action);
if (__DEV__) {
isInHookUserCodeInDev = false;
}
update = update.next;
} while (update !== null);
workInProgressHook.memoizedState = newState;
return [newState, dispatch];
}
}
return [workInProgressHook.memoizedState, dispatch];
} else {
if (__DEV__) {
isInHookUserCodeInDev = true;
}
let initialState;
if (reducer === basicStateReducer) {
// Special case for `useState`.
initialState =
typeof initialArg === 'function'
? ((initialArg: any): () => S)()
: ((initialArg: any): S);
} else {
initialState =
init !== undefined ? init(initialArg) : ((initialArg: any): S);
}
if (__DEV__) {
isInHookUserCodeInDev = false;
}
workInProgressHook.memoizedState = initialState;
const queue: UpdateQueue<A> = (workInProgressHook.queue = {
last: null,
dispatch: null,
});
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingComponent,
queue,
): any));
return [workInProgressHook.memoizedState, dispatch];
}
}
function useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
if (workInProgressHook !== null) {
const prevState = workInProgressHook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
}
if (__DEV__) {
isInHookUserCodeInDev = true;
}
const nextValue = nextCreate();
if (__DEV__) {
isInHookUserCodeInDev = false;
}
workInProgressHook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function useRef<T>(initialValue: T): {|current: T|} {
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
const previousRef = workInProgressHook.memoizedState;
if (previousRef === null) {
const ref = {current: initialValue};
if (__DEV__) {
Object.seal(ref);
}
workInProgressHook.memoizedState = ref;
return ref;
} else {
return previousRef;
}
}
function useInsertionEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
) {
if (__DEV__) {
currentHookNameInDev = 'useInsertionEffect';
console.error(
'useInsertionEffect does nothing on the server, because its effect cannot ' +
"be encoded into the server renderer's output format. This will lead " +
'to a mismatch between the initial, non-hydrated UI and the intended ' +
'UI. To avoid this, useInsertionEffect should only be used in ' +
'components that render exclusively on the client.',
);
}
}
export function useLayoutEffect(
create: () => (() => void) | void,
inputs: Array<mixed> | void | null,
) {
if (__DEV__) {
currentHookNameInDev = 'useLayoutEffect';
console.error(
'useLayoutEffect does nothing on the server, because its effect cannot ' +
"be encoded into the server renderer's output format. This will lead " +
'to a mismatch between the initial, non-hydrated UI and the intended ' +
'UI. To avoid this, useLayoutEffect should only be used in ' +
'components that render exclusively on the client. ' +
'See https://reactjs.org/link/uselayouteffect-ssr for common fixes.',
);
}
}
function dispatchAction<A>(
componentIdentity: Object,
queue: UpdateQueue<A>,
action: A,
) {
if (numberOfReRenders >= RE_RENDER_LIMIT) {
throw new Error(
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
}
if (componentIdentity === currentlyRenderingComponent) {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
const update: Update<A> = {
action,
next: null,
};
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
} else {
// Append the update to the end of the list.
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
}
lastRenderPhaseUpdate.next = update;
}
} else {
// This means an update has happened after the function component has
// returned. On the server this is a no-op. In React Fiber, the update
// would be scheduled for a future render.
}
}
export function useCallback<T>(
callback: T,
deps: Array<mixed> | void | null,
): T {
return useMemo(() => callback, deps);
}
// TODO Decide on how to implement this hook for server rendering.
// If a mutation occurs during render, consider triggering a Suspense boundary
// and falling back to client rendering.
function useMutableSource<Source, Snapshot>(
source: MutableSource<Source>,
getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
): Snapshot {
resolveCurrentlyRenderingComponent();
return getSnapshot(source._source);
}
function useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
if (getServerSnapshot === undefined) {
throw new Error(
'Missing getServerSnapshot, which is required for ' +
'server-rendered content. Will revert to client rendering.',
);
}
return getServerSnapshot();
}
function useDeferredValue<T>(value: T): T {
resolveCurrentlyRenderingComponent();
return value;
}
function useTransition(): [boolean, (callback: () => void) => void] {
resolveCurrentlyRenderingComponent();
const startTransition = callback => {
callback();
};
return [false, startTransition];
}
function useId(): string {
throw new Error('Not implemented.');
}
function useCacheRefresh(): <T>(?() => T, ?T) => void {
throw new Error('Not implemented.');
}
function noop(): void {}
export let currentPartialRenderer: PartialRenderer = (null: any);
export function setCurrentPartialRenderer(renderer: PartialRenderer) {
currentPartialRenderer = renderer;
}
export const Dispatcher: DispatcherType = {
readContext,
useContext,
useMemo,
useReducer,
useRef,
useState,
useInsertionEffect,
useLayoutEffect,
useCallback,
// useImperativeHandle is not run in the server environment
useImperativeHandle: noop,
// Effects are not run in the server environment.
useEffect: noop,
// Debugging effect
useDebugValue: noop,
useDeferredValue,
useTransition,
useId,
// Subscriptions are not setup in a server environment.
useMutableSource,
useSyncExternalStore,
};
if (enableCache) {
Dispatcher.getCacheSignal = getCacheSignal;
Dispatcher.getCacheForType = getCacheForType;
Dispatcher.useCacheRefresh = useCacheRefresh;
}