@samitouri / QOS-React-2 / commits / 4f76a28c93

[Fizz] Implement New Context (#21255)

* Add NewContext module This implements a reverse linked list tree containing the previous contexts. * Implement recursive algorithm This algorithm pops the contexts back to a shared ancestor on the way down the stack and then pushes new contexts in reverse order up the stack. * Move isPrimaryRenderer to ServerFormatConfig This is primarily intended to be used to support renderToString with a separate build than the main one. This allows them to be nested. * Wire up more element type matchers * Wire up Context Provider type * Wire up Context Consumer * Test * Implement reader in class * Update error codez

Sebastian Markbåge committed Apr 14, 2021 at 14:45 UTC 4f76a28c93c56e3f0c03150460fd4236880e0c94
8 files changed +567 -34
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+66
@@ -727,4 +727,70 @@ describe('ReactDOMFizzServer', () => {
727 </div>,
728 );
729 });
730 +
731 + // @gate experimental
732 + it('should resume the context from where it left off', async () => {
733 + const ContextA = React.createContext('A0');
734 + const ContextB = React.createContext('B0');
735 +
736 + function PrintA() {
737 + return (
738 + <ContextA.Consumer>{value => <Text text={value} />}</ContextA.Consumer>
739 + );
740 + }
741 +
742 + class PrintB extends React.Component {
743 + static contextType = ContextB;
744 + render() {
745 + return <Text text={this.context} />;
746 + }
747 + }
748 +
749 + function AsyncParent({text, children}) {
750 + return (
751 + <>
752 + <AsyncText text={text} />
753 + <b>{children}</b>
754 + </>
755 + );
756 + }
757 +
758 + await act(async () => {
759 + const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
760 + <div>
761 + <PrintA />
762 + <div>
763 + <ContextA.Provider value="A0.1">
764 + <Suspense fallback={<Text text="Loading..." />}>
765 + <AsyncParent text="Child:">
766 + <PrintA />
767 + </AsyncParent>
768 + <PrintB />
769 + </Suspense>
770 + </ContextA.Provider>
771 + </div>
772 + <PrintA />
773 + </div>,
774 + writable,
775 + );
776 + startWriting();
777 + });
778 + expect(getVisibleChildren(container)).toEqual(
779 + <div>
780 + A0<div>Loading...</div>A0
781 + </div>,
782 + );
783 + await act(async () => {
784 + resolveText('Child:');
785 + });
786 + expect(getVisibleChildren(container)).toEqual(
787 + <div>
788 + A0
789 + <div>
790 + Child:<b>A0.1</b>B0
791 + </div>
792 + A0
793 + </div>,
794 + );
795 + });
796 });
packages/react-dom/src/server/ReactDOMServerFormatConfig.js
+4
@@ -48,6 +48,10 @@ import hasOwnProperty from 'shared/hasOwnProperty';
48 import sanitizeURL from '../shared/sanitizeURL';
49 import isArray from 'shared/isArray';
50
51 +// Used to distinguish these contexts from ones used in other renderers.
52 +// E.g. this can be used to distinguish legacy renderers from this modern one.
53 +export const isPrimaryRenderer = true;
54 +
55 // Per response, global state that is not contextual to the rendering subtree.
56 export type ResponseState = {
57 placeholderPrefix: PrecomputedChunk,
packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js
+2
@@ -23,6 +23,8 @@ import {
23
24 import invariant from 'shared/invariant';
25
26 +export const isPrimaryRenderer = true;
27 +
28 // Every list of children or string is null terminated.
29 const END_TAG = 0;
30 // Tree node tags.
packages/react-server/src/ReactFizzClassComponent.js
+3 -6
@@ -8,6 +8,7 @@
8 */
9
10 import {emptyContextObject} from './ReactFizzContext';
11 +import {readContext} from './ReactFizzNewContext';
12
13 import {disableLegacyContext} from 'shared/ReactFeatureFlags';
14 import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap';
@@ -211,9 +212,7 @@ export function constructClassInstance(
212 }
213
214 if (typeof contextType === 'object' && contextType !== null) {
214 - // TODO: Implement Context.
215 - // context = readContext((contextType: any));
216 - throw new Error('Context is not yet implemented.');
215 + context = readContext((contextType: any));
216 } else if (!disableLegacyContext) {
217 context = maskedLegacyContext;
218 }
@@ -617,9 +616,7 @@ export function mountClassInstance(
616
617 const contextType = ctor.contextType;
618 if (typeof contextType === 'object' && contextType !== null) {
620 - // TODO: Implement Context.
621 - // instance.context = readContext(contextType);
622 - throw new Error('Context is not yet implemented.');
619 + instance.context = readContext(contextType);
620 } else if (disableLegacyContext) {
621 instance.context = emptyContextObject;
622 } else {
packages/react-server/src/ReactFizzNewContext.js new
+278
@@ -0,0 +1,278 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {ReactContext} from 'shared/ReactTypes';
11 +
12 +import {isPrimaryRenderer} from './ReactServerFormatConfig';
13 +
14 +import invariant from 'shared/invariant';
15 +
16 +let rendererSigil;
17 +if (__DEV__) {
18 + // Use this to detect multiple renderers using the same context
19 + rendererSigil = {};
20 +}
21 +
22 +// Used to store the parent path of all context overrides in a shared linked list.
23 +// Forming a reverse tree.
24 +type ContextNode<T> = {
25 + parent: null | ContextNode<any>,
26 + depth: number, // Short hand to compute the depth of the tree at this node.
27 + context: ReactContext<T>,
28 + parentValue: T,
29 + value: T,
30 +};
31 +
32 +// The structure of a context snapshot is an implementation of this file.
33 +// Currently, it's implemented as tracking the current active node.
34 +export opaque type ContextSnapshot = null | ContextNode<any>;
35 +
36 +export const rootContextSnapshot: ContextSnapshot = null;
37 +
38 +// We assume that this runtime owns the "current" field on all ReactContext instances.
39 +// This global (actually thread local) state represents what state all those "current",
40 +// fields are currently in.
41 +let currentActiveSnapshot: ContextSnapshot = null;
42 +
43 +function popNode(prev: ContextNode<any>): void {
44 + if (isPrimaryRenderer) {
45 + prev.context._currentValue = prev.parentValue;
46 + } else {
47 + prev.context._currentValue2 = prev.parentValue;
48 + }
49 +}
50 +
51 +function pushNode(next: ContextNode<any>): void {
52 + if (isPrimaryRenderer) {
53 + next.context._currentValue = next.value;
54 + } else {
55 + next.context._currentValue2 = next.value;
56 + }
57 +}
58 +
59 +function popToNearestCommonAncestor(
60 + prev: ContextNode<any>,
61 + next: ContextNode<any>,
62 +): void {
63 + if (prev === next) {
64 + // We've found a shared ancestor. We don't need to pop nor reapply this one or anything above.
65 + } else {
66 + popNode(prev);
67 + const parentPrev = prev.parent;
68 + const parentNext = next.parent;
69 + if (parentPrev === null) {
70 + invariant(
71 + parentNext === null,
72 + 'The stacks must reach the root at the same time. This is a bug in React.',
73 + );
74 + } else {
75 + invariant(
76 + parentNext !== null,
77 + 'The stacks must reach the root at the same time. This is a bug in React.',
78 + );
79 + popToNearestCommonAncestor(parentPrev, parentNext);
80 + // On the way back, we push the new ones that weren't common.
81 + pushNode(next);
82 + }
83 + }
84 +}
85 +
86 +function popAllPrevious(prev: ContextNode<any>): void {
87 + popNode(prev);
88 + const parentPrev = prev.parent;
89 + if (parentPrev !== null) {
90 + popAllPrevious(parentPrev);
91 + }
92 +}
93 +
94 +function pushAllNext(next: ContextNode<any>): void {
95 + const parentNext = next.parent;
96 + if (parentNext !== null) {
97 + pushAllNext(parentNext);
98 + }
99 + pushNode(next);
100 +}
101 +
102 +function popPreviousToCommonLevel(
103 + prev: ContextNode<any>,
104 + next: ContextNode<any>,
105 +): void {
106 + popNode(prev);
107 + const parentPrev = prev.parent;
108 + invariant(
109 + parentPrev !== null,
110 + 'The depth must equal at least at zero before reaching the root. This is a bug in React.',
111 + );
112 + if (parentPrev.depth === next.depth) {
113 + // We found the same level. Now we just need to find a shared ancestor.
114 + popToNearestCommonAncestor(parentPrev, next);
115 + } else {
116 + // We must still be deeper.
117 + popPreviousToCommonLevel(parentPrev, next);
118 + }
119 +}
120 +
121 +function popNextToCommonLevel(
122 + prev: ContextNode<any>,
123 + next: ContextNode<any>,
124 +): void {
125 + const parentNext = next.parent;
126 + invariant(
127 + parentNext !== null,
128 + 'The depth must equal at least at zero before reaching the root. This is a bug in React.',
129 + );
130 + if (prev.depth === parentNext.depth) {
131 + // We found the same level. Now we just need to find a shared ancestor.
132 + popToNearestCommonAncestor(prev, parentNext);
133 + } else {
134 + // We must still be deeper.
135 + popNextToCommonLevel(prev, parentNext);
136 + }
137 + pushNode(next);
138 +}
139 +
140 +// Perform context switching to the new snapshot.
141 +// To make it cheap to read many contexts, while not suspending, we make the switch eagerly by
142 +// updating all the context's current values. That way reads, always just read the current value.
143 +// At the cost of updating contexts even if they're never read by this subtree.
144 +export function switchContext(newSnapshot: ContextSnapshot): void {
145 + // The basic algorithm we need to do is to pop back any contexts that are no longer on the stack.
146 + // We also need to update any new contexts that are now on the stack with the deepest value.
147 + // The easiest way to update new contexts is to just reapply them in reverse order from the
148 + // perspective of the backpointers. To avoid allocating a lot when switching, we use the stack
149 + // for that. Therefore this algorithm is recursive.
150 + // 1) First we pop which ever snapshot tree was deepest. Popping old contexts as we go.
151 + // 2) Then we find the nearest common ancestor from there. Popping old contexts as we go.
152 + // 3) Then we reapply new contexts on the way back up the stack.
153 + const prev = currentActiveSnapshot;
154 + const next = newSnapshot;
155 + if (prev !== next) {
156 + if (prev === null) {
157 + // $FlowFixMe: This has to be non-null since it's not equal to prev.
158 + pushAllNext(next);
159 + } else if (next === null) {
160 + popAllPrevious(prev);
161 + } else if (prev.depth === next.depth) {
162 + popToNearestCommonAncestor(prev, next);
163 + } else if (prev.depth > next.depth) {
164 + popPreviousToCommonLevel(prev, next);
165 + } else {
166 + popNextToCommonLevel(prev, next);
167 + }
168 + currentActiveSnapshot = next;
169 + }
170 +}
171 +
172 +export function pushProvider<T>(
173 + context: ReactContext<T>,
174 + nextValue: T,
175 +): ContextSnapshot {
176 + let prevValue;
177 + if (isPrimaryRenderer) {
178 + prevValue = context._currentValue;
179 + context._currentValue = nextValue;
180 + if (__DEV__) {
181 + if (
182 + context._currentRenderer !== undefined &&
183 + context._currentRenderer !== null &&
184 + context._currentRenderer !== rendererSigil
185 + ) {
186 + console.error(
187 + 'Detected multiple renderers concurrently rendering the ' +
188 + 'same context provider. This is currently unsupported.',
189 + );
190 + }
191 + context._currentRenderer = rendererSigil;
192 + }
193 + } else {
194 + prevValue = context._currentValue2;
195 + context._currentValue2 = nextValue;
196 + if (__DEV__) {
197 + if (
198 + context._currentRenderer2 !== undefined &&
199 + context._currentRenderer2 !== null &&
200 + context._currentRenderer2 !== rendererSigil
201 + ) {
202 + console.error(
203 + 'Detected multiple renderers concurrently rendering the ' +
204 + 'same context provider. This is currently unsupported.',
205 + );
206 + }
207 + context._currentRenderer2 = rendererSigil;
208 + }
209 + }
210 + const prevNode = currentActiveSnapshot;
211 + const newNode: ContextNode<T> = {
212 + parent: prevNode,
213 + depth: prevNode === null ? 0 : prevNode.depth + 1,
214 + context: context,
215 + parentValue: prevValue,
216 + value: nextValue,
217 + };
218 + currentActiveSnapshot = newNode;
219 + return newNode;
220 +}
221 +
222 +export function popProvider<T>(context: ReactContext<T>): ContextSnapshot {
223 + const prevSnapshot = currentActiveSnapshot;
224 + invariant(
225 + prevSnapshot !== null,
226 + 'Tried to pop a Context at the root of the app. This is a bug in React.',
227 + );
228 + if (__DEV__) {
229 + if (prevSnapshot.context !== context) {
230 + console.error(
231 + 'The parent context is not the expected context. This is probably a bug in React.',
232 + );
233 + }
234 + }
235 + if (isPrimaryRenderer) {
236 + prevSnapshot.context._currentValue = prevSnapshot.parentValue;
237 + if (__DEV__) {
238 + if (
239 + context._currentRenderer !== undefined &&
240 + context._currentRenderer !== null &&
241 + context._currentRenderer !== rendererSigil
242 + ) {
243 + console.error(
244 + 'Detected multiple renderers concurrently rendering the ' +
245 + 'same context provider. This is currently unsupported.',
246 + );
247 + }
248 + context._currentRenderer = rendererSigil;
249 + }
250 + } else {
251 + prevSnapshot.context._currentValue2 = prevSnapshot.parentValue;
252 + if (__DEV__) {
253 + if (
254 + context._currentRenderer2 !== undefined &&
255 + context._currentRenderer2 !== null &&
256 + context._currentRenderer2 !== rendererSigil
257 + ) {
258 + console.error(
259 + 'Detected multiple renderers concurrently rendering the ' +
260 + 'same context provider. This is currently unsupported.',
261 + );
262 + }
263 + context._currentRenderer2 = rendererSigil;
264 + }
265 + }
266 + return (currentActiveSnapshot = prevSnapshot.parent);
267 +}
268 +
269 +export function getActiveContext(): ContextSnapshot {
270 + return currentActiveSnapshot;
271 +}
272 +
273 +export function readContext<T>(context: ReactContext<T>): T {
274 + const value = isPrimaryRenderer
275 + ? context._currentValue
276 + : context._currentValue2;
277 + return value;
278 +}
packages/react-server/src/ReactFizzServer.js
+208 -27
@@ -13,12 +13,18 @@ import type {
13 Chunk,
14 PrecomputedChunk,
15 } from './ReactServerStreamConfig';
16 -import type {ReactNodeList} from 'shared/ReactTypes';
16 +import type {
17 + ReactNodeList,
18 + ReactContext,
19 + ReactProviderType,
20 +} from 'shared/ReactTypes';
21 +import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
22 import type {
23 SuspenseBoundaryID,
24 ResponseState,
25 FormatContext,
26 } from './ReactServerFormatConfig';
27 +import type {ContextSnapshot} from './ReactFizzNewContext';
28
29 import {
30 scheduleWork,
@@ -56,6 +62,15 @@ import {
62 processChildContext,
63 emptyContextObject,
64 } from './ReactFizzContext';
65 +import {
66 + readContext,
67 + rootContextSnapshot,
68 + switchContext,
69 + getActiveContext,
70 + pushProvider,
71 + popProvider,
72 +} from './ReactFizzNewContext';
73 +
74 import {
75 getIteratorFn,
76 REACT_ELEMENT_TYPE,
@@ -68,6 +83,10 @@ import {
83 REACT_PROFILER_TYPE,
84 REACT_SUSPENSE_LIST_TYPE,
85 REACT_FRAGMENT_TYPE,
86 + REACT_FORWARD_REF_TYPE,
87 + REACT_MEMO_TYPE,
88 + REACT_PROVIDER_TYPE,
89 + REACT_CONTEXT_TYPE,
90 } from 'shared/ReactSymbols';
91 import ReactSharedInternals from 'shared/ReactSharedInternals';
92 import {
@@ -104,6 +123,7 @@ type Task = {
123 blockedSegment: Segment, // the segment we'll write to
124 abortSet: Set<Task>, // the abortable set that this task belongs to
125 legacyContext: LegacyContext, // the current legacy context that this task is executing in
126 + context: ContextSnapshot, // the current new context that this task is executing in
127 assignID: null | SuspenseBoundaryID, // id to assign to the content
128 };
129
@@ -220,6 +240,7 @@ export function createRequest(
240 rootSegment,
241 abortSet,
242 emptyContextObject,
243 + rootContextSnapshot,
244 null,
245 );
246 pingedTasks.push(rootTask);
@@ -257,6 +278,7 @@ function createTask(
278 blockedSegment: Segment,
279 abortSet: Set<Task>,
280 legacyContext: LegacyContext,
281 + context: ContextSnapshot,
282 assignID: null | SuspenseBoundaryID,
283 ): Task {
284 request.allPendingTasks++;
@@ -272,6 +294,7 @@ function createTask(
294 blockedSegment,
295 abortSet,
296 legacyContext,
297 + context,
298 assignID,
299 };
300 abortSet.add(task);
@@ -394,6 +417,7 @@ function renderSuspenseBoundary(
417 boundarySegment,
418 fallbackAbortSet,
419 task.legacyContext,
420 + task.context,
421 newBoundary.id, // This is the ID we want to give this fallback so we can replace it later.
422 );
423 // TODO: This should be queued at a separate lower priority queue so that we only work
@@ -529,6 +553,7 @@ let didWarnAboutReassigningProps = false;
553 const didWarnAboutDefaultPropsOnFunctionComponent = {};
554 let didWarnAboutGenerators = false;
555 let didWarnAboutMaps = false;
556 +let hasWarnedAboutUsingContextAsConsumer = false;
557
558 // This would typically be a function component but we still support module pattern
559 // components for some reason.
@@ -694,49 +719,185 @@ function validateFunctionComponentInDev(Component: any): void {
719 }
720 }
721
722 +function renderForwardRef(
723 + request: Request,
724 + task: Task,
725 + type: any,
726 + props: Object,
727 +): void {
728 + throw new Error('Not yet implemented element type.');
729 +}
730 +
731 +function renderMemo(
732 + request: Request,
733 + task: Task,
734 + type: any,
735 + props: Object,
736 +): void {
737 + throw new Error('Not yet implemented element type.');
738 +}
739 +
740 +function renderContextConsumer(
741 + request: Request,
742 + task: Task,
743 + context: ReactContext<any>,
744 + props: Object,
745 +): void {
746 + // The logic below for Context differs depending on PROD or DEV mode. In
747 + // DEV mode, we create a separate object for Context.Consumer that acts
748 + // like a proxy to Context. This proxy object adds unnecessary code in PROD
749 + // so we use the old behaviour (Context.Consumer references Context) to
750 + // reduce size and overhead. The separate object references context via
751 + // a property called "_context", which also gives us the ability to check
752 + // in DEV mode if this property exists or not and warn if it does not.
753 + if (__DEV__) {
754 + if ((context: any)._context === undefined) {
755 + // This may be because it's a Context (rather than a Consumer).
756 + // Or it may be because it's older React where they're the same thing.
757 + // We only want to warn if we're sure it's a new React.
758 + if (context !== context.Consumer) {
759 + if (!hasWarnedAboutUsingContextAsConsumer) {
760 + hasWarnedAboutUsingContextAsConsumer = true;
761 + console.error(
762 + 'Rendering <Context> directly is not supported and will be removed in ' +
763 + 'a future major release. Did you mean to render <Context.Consumer> instead?',
764 + );
765 + }
766 + }
767 + } else {
768 + context = (context: any)._context;
769 + }
770 + }
771 + const render = props.children;
772 +
773 + if (__DEV__) {
774 + if (typeof render !== 'function') {
775 + console.error(
776 + 'A context consumer was rendered with multiple children, or a child ' +
777 + "that isn't a function. A context consumer expects a single child " +
778 + 'that is a function. If you did pass a function, make sure there ' +
779 + 'is no trailing or leading whitespace around it.',
780 + );
781 + }
782 + }
783 +
784 + const newValue = readContext(context);
785 + const newChildren = render(newValue);
786 +
787 + renderNodeDestructive(request, task, newChildren);
788 +}
789 +
790 +function renderContextProvider(
791 + request: Request,
792 + task: Task,
793 + type: ReactProviderType<any>,
794 + props: Object,
795 +): void {
796 + const context = type._context;
797 + const value = props.value;
798 + const children = props.children;
799 + let prevSnapshot;
800 + if (__DEV__) {
801 + prevSnapshot = task.context;
802 + }
803 + task.context = pushProvider(context, value);
804 + renderNodeDestructive(request, task, children);
805 + task.context = popProvider(context);
806 + if (__DEV__) {
807 + if (prevSnapshot !== task.context) {
808 + console.error(
809 + 'Popping the context provider did not return back to the original snapshot. This is a bug in React.',
810 + );
811 + }
812 + }
813 +}
814 +
815 +function renderLazyComponent(
816 + request: Request,
817 + task: Task,
818 + type: LazyComponentType<any, any>,
819 + props: Object,
820 +): void {
821 + throw new Error('Not yet implemented element type.');
822 +}
823 +
824 function renderElement(
825 request: Request,
826 task: Task,
827 type: any,
828 props: Object,
702 - node: ReactNodeList,
829 ): void {
830 if (typeof type === 'function') {
831 if (shouldConstruct(type)) {
832 renderClassComponent(request, task, type, props);
833 + return;
834 } else {
835 renderIndeterminateComponent(request, task, type, props);
836 + return;
837 }
710 - } else if (typeof type === 'string') {
838 + }
839 + if (typeof type === 'string') {
840 renderHostElement(request, task, type, props);
712 - } else {
713 - switch (type) {
714 - // TODO: LegacyHidden acts the same as a fragment. This only works
715 - // because we currently assume that every instance of LegacyHidden is
716 - // accompanied by a host component wrapper. In the hidden mode, the host
717 - // component is given a `hidden` attribute, which ensures that the
718 - // initial HTML is not visible. To support the use of LegacyHidden as a
719 - // true fragment, without an extra DOM node, we would have to hide the
720 - // initial HTML in some other way.
721 - // TODO: Add REACT_OFFSCREEN_TYPE here too with the same capability.
722 - case REACT_LEGACY_HIDDEN_TYPE:
723 - case REACT_DEBUG_TRACING_MODE_TYPE:
724 - case REACT_STRICT_MODE_TYPE:
725 - case REACT_PROFILER_TYPE:
726 - case REACT_SUSPENSE_LIST_TYPE: // TODO: SuspenseList should control the boundaries.
727 - case REACT_FRAGMENT_TYPE: {
728 - renderNodeDestructive(request, task, props.children);
729 - break;
841 + return;
842 + }
843 +
844 + switch (type) {
845 + // TODO: LegacyHidden acts the same as a fragment. This only works
846 + // because we currently assume that every instance of LegacyHidden is
847 + // accompanied by a host component wrapper. In the hidden mode, the host
848 + // component is given a `hidden` attribute, which ensures that the
849 + // initial HTML is not visible. To support the use of LegacyHidden as a
850 + // true fragment, without an extra DOM node, we would have to hide the
851 + // initial HTML in some other way.
852 + // TODO: Add REACT_OFFSCREEN_TYPE here too with the same capability.
853 + case REACT_LEGACY_HIDDEN_TYPE:
854 + case REACT_DEBUG_TRACING_MODE_TYPE:
855 + case REACT_STRICT_MODE_TYPE:
856 + case REACT_PROFILER_TYPE:
857 + case REACT_SUSPENSE_LIST_TYPE: // TODO: SuspenseList should control the boundaries.
858 + case REACT_FRAGMENT_TYPE: {
859 + renderNodeDestructive(request, task, props.children);
860 + return;
861 + }
862 + case REACT_SUSPENSE_TYPE: {
863 + renderSuspenseBoundary(request, task, props);
864 + return;
865 + }
866 + }
867 +
868 + if (typeof type === 'object' && type !== null) {
869 + switch (type.$$typeof) {
870 + case REACT_FORWARD_REF_TYPE: {
871 + renderForwardRef(request, task, type, props);
872 + return;
873 }
731 - case REACT_SUSPENSE_TYPE: {
732 - renderSuspenseBoundary(request, task, props);
733 - break;
874 + case REACT_MEMO_TYPE: {
875 + renderMemo(request, task, type, props);
876 + return;
877 }
735 - default: {
736 - throw new Error('Not yet implemented element type.');
878 + case REACT_PROVIDER_TYPE: {
879 + renderContextProvider(request, task, type, props);
880 + return;
881 + }
882 + case REACT_CONTEXT_TYPE: {
883 + renderContextConsumer(request, task, type, props);
884 + return;
885 + }
886 + case REACT_LAZY_TYPE: {
887 + renderLazyComponent(request, task, type, props);
888 + return;
889 }
890 }
891 }
892 +
893 + invariant(
894 + false,
895 + 'Element type is invalid: expected a string (for built-in ' +
896 + 'components) or a class/function (for composite components) ' +
897 + 'but got: %s.%s',
898 + type == null ? type : typeof type,
899 + '',
900 + );
901 }
902
903 function validateIterable(iterable, iteratorFn: Function): void {
@@ -791,7 +952,7 @@ function renderNodeDestructive(
952 const element: React$Element<any> = (node: any);
953 const type = element.type;
954 const props = element.props;
794 - renderElement(request, task, type, props, node);
955 + renderElement(request, task, type, props);
956 return;
957 }
958 case REACT_PORTAL_TYPE:
@@ -918,6 +1079,7 @@ function spawnNewSuspendedTask(
1079 newSegment,
1080 task.abortSet,
1081 task.legacyContext,
1082 + task.context,
1083 task.assignID,
1084 );
1085 // We've delegated the assignment.
@@ -936,6 +1098,7 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
1098 // process.
1099 const previousFormatContext = task.blockedSegment.formatContext;
1100 const previousLegacyContext = task.legacyContext;
1101 + const previousContext = task.context;
1102 try {
1103 return renderNodeDestructive(request, task, node);
1104 } catch (x) {
@@ -945,6 +1108,9 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
1108 // functions in case nothing throws so we don't use "finally" here.
1109 task.blockedSegment.formatContext = previousFormatContext;
1110 task.legacyContext = previousLegacyContext;
1111 + task.context = previousContext;
1112 + // Restore all active ReactContexts to what they were before.
1113 + switchContext(previousContext);
1114 } else {
1115 // We assume that we don't need the correct context.
1116 // Let's terminate the rest of the tree and don't render any siblings.
@@ -1104,6 +1270,10 @@ function retryTask(request: Request, task: Task): void {
1270 // We completed this by other means before we had a chance to retry it.
1271 return;
1272 }
1273 + // We restore the context to what it was when we suspended.
1274 + // We don't restore it after we leave because it's likely that we'll end up
1275 + // needing a very similar context soon again.
1276 + switchContext(task.context);
1277 try {
1278 // We call the destructive form that mutates this task. That way if something
1279 // suspends again, we can reuse the same task instead of spawning a new one.
@@ -1129,6 +1299,7 @@ function performWork(request: Request): void {
1299 if (request.status === CLOSED) {
1300 return;
1301 }
1302 + const prevContext = getActiveContext();
1303 const prevDispatcher = ReactCurrentDispatcher.current;
1304 ReactCurrentDispatcher.current = Dispatcher;
1305
@@ -1148,6 +1319,16 @@ function performWork(request: Request): void {
1319 fatalError(request, error);
1320 } finally {
1321 ReactCurrentDispatcher.current = prevDispatcher;
1322 + if (prevDispatcher === Dispatcher) {
1323 + // This means that we were in a reentrant work loop. This could happen
1324 + // in a renderer that supports synchronous work like renderToString,
1325 + // when it's called from within another renderer.
1326 + // Normally we don't bother switching the contexts to their root/default
1327 + // values when leaving because we'll likely need the same or similar
1328 + // context again. However, when we're inside a synchronous loop like this
1329 + // we'll to restore the context to what it was before returning.
1330 + switchContext(prevContext);
1331 + }
1332 }
1333 }
1334
packages/react-server/src/forks/ReactServerFormatConfig.custom.js
+2
@@ -29,6 +29,8 @@ export opaque type ResponseState = mixed;
29 export opaque type FormatContext = mixed;
30 export opaque type SuspenseBoundaryID = mixed;
31
32 +export const isPrimaryRenderer = false;
33 +
34 export const getChildFormatContext = $$$hostConfig.getChildFormatContext;
35 export const createSuspenseBoundaryID = $$$hostConfig.createSuspenseBoundaryID;
36 export const pushEmpty = $$$hostConfig.pushEmpty;
scripts/error-codes/codes.json
+4 -1
@@ -388,5 +388,8 @@
388 "397": "Unknown insertion mode. This is a bug in React.",
389 "398": "`dangerouslySetInnerHTML` does not work on <option>.",
390 "399": "%s is a self-closing tag and must neither have `children` nor use `dangerouslySetInnerHTML`.",
391 - "400": "menuitems cannot have `children` nor `dangerouslySetInnerHTML`."
391 + "400": "menuitems cannot have `children` nor `dangerouslySetInnerHTML`.",
392 + "401": "The stacks must reach the root at the same time. This is a bug in React.",
393 + "402": "The depth must equal at least at zero before reaching the root. This is a bug in React.",
394 + "403": "Tried to pop a Context at the root of the app. This is a bug in React."
395 }