@samitouri / QOS-React / commits / 87ae211ccd

New context API (#11818)

* New context API Introduces a declarative context API that propagates updates even when shouldComponentUpdate returns false. * Fuzz tester for context * Use ReactElement for provider and consumer children * Unify more branches in createFiberFromElement * Compare context values using Object.is Same semantics as PureComponent/shallowEqual. * Add support for Provider and Consumer to server-side renderer * Store providers on global stack Rather than using a linked list stored on the context type. The global stack can be reset in case of an interruption or error, whereas with the linked list implementation, you'd need to keep track of every context type. * Put new context API behind a feature flag We'll enable this in www only for now. * Store nearest provider on context object * Handle reentrancy in server renderer Context stack should be per server renderer instance. * Bailout of consumer updates using bitmask The context type defines an optional function that compares two context values, returning a bitfield. A consumer may specify the bits it needs for rendering. If a provider's context changes, and the consumer's bits do not intersect with the changed bits, we can skip the consumer. This is similar to how selectors are used in Redux but fast enough to do while scanning the tree. The only user code involved is the function that computes the changed bits. But that's only called once per provider update, not for every consumer. * Store current value and changed bits on context object There are fewer providers than consumers, so better to do this work at the provider. * Use maximum of 31 bits for bitmask This is the largest integer size in V8 on 32-bit systems. Warn in development if too large a number is used. * ProviderComponent -> ContextProvider, ConsumerComponent -> ContextConsumer * Inline Object.is * Warn if multiple renderers concurrently render the same context provider Let's see if we can get away with not supporting this for now. If it turns out that it's needed, we can fall back to backtracking the fiber return path. * Nits that came up during review

Andrew Clark committed Jan 24, 2018 at 19:36 UTC 87ae211ccd8d61796cfdef138d1e12fb7a74f85d
21 files changed +1607 -108
package.json
+1
@@ -80,6 +80,7 @@
80 "platform": "^1.1.0",
81 "prettier": "1.8.1",
82 "prop-types": "^15.6.0",
83 + "random-seed": "^0.3.0",
84 "rimraf": "^2.6.1",
85 "rollup": "^0.52.1",
86 "rollup-plugin-babel": "^3.0.1",
packages/react-dom/src/__tests__/ReactServerRendering-test.internal.js renamed
+96
@@ -14,6 +14,7 @@ let React;
14 let ReactCallReturn;
15 let ReactDOMServer;
16 let PropTypes;
17 +let ReactFeatureFlags;
18
19 function normalizeCodeLocInfo(str) {
20 return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
@@ -22,6 +23,8 @@ function normalizeCodeLocInfo(str) {
23 describe('ReactDOMServer', () => {
24 beforeEach(() => {
25 jest.resetModules();
26 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
27 + ReactFeatureFlags.enableNewContextAPI = true;
28 React = require('react');
29 ReactCallReturn = require('react-call-return');
30 PropTypes = require('prop-types');
@@ -384,6 +387,99 @@ describe('ReactDOMServer', () => {
387 expect(markup).toContain('hello, world');
388 });
389
390 + it('renders with new context API', () => {
391 + const Context = React.unstable_createContext(0);
392 +
393 + function Provider(props) {
394 + return Context.provide(props.value, props.children);
395 + }
396 +
397 + function Consumer(props) {
398 + return Context.consume(value => {
399 + return 'Result: ' + value;
400 + });
401 + }
402 +
403 + const Indirection = React.Fragment;
404 +
405 + function App(props) {
406 + return (
407 + <Provider value={props.value}>
408 + <Provider value={2}>
409 + <Consumer />
410 + </Provider>
411 + <Indirection>
412 + <Indirection>
413 + <Consumer />
414 + <Provider value={3}>
415 + <Consumer />
416 + </Provider>
417 + </Indirection>
418 + </Indirection>
419 + <Consumer />
420 + </Provider>
421 + );
422 + }
423 +
424 + const markup = ReactDOMServer.renderToString(<App value={1} />);
425 + // Extract the numbers rendered by the consumers
426 + const results = markup.match(/\d+/g).map(Number);
427 + expect(results).toEqual([2, 1, 3, 1]);
428 + });
429 +
430 + it('renders context API, reentrancy', () => {
431 + const Context = React.unstable_createContext(0);
432 +
433 + function Provider(props) {
434 + return Context.provide(props.value, props.children);
435 + }
436 +
437 + function Consumer(props) {
438 + return Context.consume(value => {
439 + return 'Result: ' + value;
440 + });
441 + }
442 +
443 + let reentrantMarkup;
444 + function Reentrant() {
445 + reentrantMarkup = ReactDOMServer.renderToString(
446 + <App value={1} reentrant={false} />,
447 + );
448 + return null;
449 + }
450 +
451 + const Indirection = React.Fragment;
452 +
453 + function App(props) {
454 + return (
455 + <Provider value={props.value}>
456 + {props.reentrant && <Reentrant />}
457 + <Provider value={2}>
458 + <Consumer />
459 + </Provider>
460 + <Indirection>
461 + <Indirection>
462 + <Consumer />
463 + <Provider value={3}>
464 + <Consumer />
465 + </Provider>
466 + </Indirection>
467 + </Indirection>
468 + <Consumer />
469 + </Provider>
470 + );
471 + }
472 +
473 + const markup = ReactDOMServer.renderToString(
474 + <App value={1} reentrant={true} />,
475 + );
476 + // Extract the numbers rendered by the consumers
477 + const results = markup.match(/\d+/g).map(Number);
478 + const reentrantResults = reentrantMarkup.match(/\d+/g).map(Number);
479 + expect(results).toEqual([2, 1, 3, 1]);
480 + expect(reentrantResults).toEqual([2, 1, 3, 1]);
481 + });
482 +
483 it('renders components with different batching strategies', () => {
484 class StaticComponent extends React.Component {
485 render() {
packages/react-dom/src/server/ReactPartialRenderer.js
+119 -5
@@ -8,6 +8,11 @@
8 */
9
10 import type {ReactElement} from 'shared/ReactElementType';
11 +import type {
12 + ReactProvider,
13 + ReactConsumer,
14 + ReactContext,
15 +} from 'shared/ReactTypes';
16
17 import React from 'react';
18 import emptyFunction from 'fbjs/lib/emptyFunction';
@@ -25,6 +30,8 @@ import {
30 REACT_CALL_TYPE,
31 REACT_RETURN_TYPE,
32 REACT_PORTAL_TYPE,
33 + REACT_PROVIDER_TYPE,
34 + REACT_CONTEXT_TYPE,
35 } from 'shared/ReactSymbols';
36
37 import {
@@ -192,7 +199,7 @@ function warnNoop(
199 const constructor = publicInstance.constructor;
200 const componentName =
201 (constructor && getComponentName(constructor)) || 'ReactClass';
195 - const warningKey = `${componentName}.${callerName}`;
202 + const warningKey = componentName + '.' + callerName;
203 if (didWarnAboutNoopUpdateForComponent[warningKey]) {
204 return;
205 }
@@ -603,6 +610,7 @@ function resolve(
610 }
611
612 type Frame = {
613 + type: mixed,
614 domNamespace: string,
615 children: FlatReactChildren,
616 childIndex: number,
@@ -622,12 +630,16 @@ class ReactDOMServerRenderer {
630 previousWasTextNode: boolean;
631 makeStaticMarkup: boolean;
632
633 + providerStack: Array<?ReactProvider<any>>;
634 + providerIndex: number;
635 +
636 constructor(children: mixed, makeStaticMarkup: boolean) {
637 const flatChildren = flattenTopLevelChildren(children);
638
639 const topFrame: Frame = {
640 // Assume all trees start in the HTML namespace (not totally true, but
641 // this is what we did historically)
642 + type: null,
643 domNamespace: Namespaces.html,
644 children: flatChildren,
645 childIndex: 0,
@@ -642,6 +654,39 @@ class ReactDOMServerRenderer {
654 this.currentSelectValue = null;
655 this.previousWasTextNode = false;
656 this.makeStaticMarkup = makeStaticMarkup;
657 +
658 + // Context (new API)
659 + this.providerStack = []; // Stack of provider objects
660 + this.providerIndex = -1;
661 + }
662 +
663 + pushProvider<T>(provider: ReactProvider<T>): void {
664 + this.providerIndex += 1;
665 + this.providerStack[this.providerIndex] = provider;
666 + const context: ReactContext<any> = provider.type.context;
667 + context.currentValue = provider.props.value;
668 + }
669 +
670 + popProvider<T>(provider: ReactProvider<T>): void {
671 + if (__DEV__) {
672 + warning(
673 + this.providerIndex > -1 &&
674 + provider === this.providerStack[this.providerIndex],
675 + 'Unexpected pop.',
676 + );
677 + }
678 + this.providerStack[this.providerIndex] = null;
679 + this.providerIndex -= 1;
680 + const context: ReactContext<any> = provider.type.context;
681 + if (this.providerIndex < 0) {
682 + context.currentValue = context.defaultValue;
683 + } else {
684 + // We assume this type is correct because of the index check above.
685 + const previousProvider: ReactProvider<any> = (this.providerStack[
686 + this.providerIndex
687 + ]: any);
688 + context.currentValue = previousProvider.props.value;
689 + }
690 }
691
692 read(bytes: number): string | null {
@@ -663,8 +708,15 @@ class ReactDOMServerRenderer {
708 this.previousWasTextNode = false;
709 }
710 this.stack.pop();
666 - if (frame.tag === 'select') {
711 + if (frame.type === 'select') {
712 this.currentSelectValue = null;
713 + } else if (
714 + frame.type != null &&
715 + frame.type.type != null &&
716 + frame.type.type.$$typeof === REACT_PROVIDER_TYPE
717 + ) {
718 + const provider: ReactProvider<any> = (frame.type: any);
719 + this.popProvider(provider);
720 }
721 continue;
722 }
@@ -723,6 +775,7 @@ class ReactDOMServerRenderer {
775 }
776 const nextChildren = toArray(nextChild);
777 const frame: Frame = {
778 + type: null,
779 domNamespace: parentNamespace,
780 children: nextChildren,
781 childIndex: 0,
@@ -738,12 +791,18 @@ class ReactDOMServerRenderer {
791 // Safe because we just checked it's an element.
792 const nextElement = ((nextChild: any): ReactElement);
793 const elementType = nextElement.type;
794 +
795 + if (typeof elementType === 'string') {
796 + return this.renderDOM(nextElement, context, parentNamespace);
797 + }
798 +
799 switch (elementType) {
742 - case REACT_FRAGMENT_TYPE:
800 + case REACT_FRAGMENT_TYPE: {
801 const nextChildren = toArray(
802 ((nextChild: any): ReactElement).props.children,
803 );
804 const frame: Frame = {
805 + type: null,
806 domNamespace: parentNamespace,
807 children: nextChildren,
808 childIndex: 0,
@@ -755,6 +814,7 @@ class ReactDOMServerRenderer {
814 }
815 this.stack.push(frame);
816 return '';
817 + }
818 case REACT_CALL_TYPE:
819 case REACT_RETURN_TYPE:
820 invariant(
@@ -764,8 +824,62 @@ class ReactDOMServerRenderer {
824 );
825 // eslint-disable-next-line-no-fallthrough
826 default:
767 - return this.renderDOM(nextElement, context, parentNamespace);
827 + break;
828 }
829 + if (typeof elementType === 'object' && elementType !== null) {
830 + switch (elementType.$$typeof) {
831 + case REACT_PROVIDER_TYPE: {
832 + const provider: ReactProvider<any> = (nextChild: any);
833 + const nextProps = provider.props;
834 + const nextChildren = toArray(nextProps.children);
835 + const frame: Frame = {
836 + type: provider,
837 + domNamespace: parentNamespace,
838 + children: nextChildren,
839 + childIndex: 0,
840 + context: context,
841 + footer: '',
842 + };
843 + if (__DEV__) {
844 + ((frame: any): FrameDev).debugElementStack = [];
845 + }
846 +
847 + this.pushProvider(provider);
848 +
849 + this.stack.push(frame);
850 + return '';
851 + }
852 + case REACT_CONTEXT_TYPE: {
853 + const consumer: ReactConsumer<any> = (nextChild: any);
854 + const nextProps = consumer.props;
855 + const nextValue = consumer.type.currentValue;
856 +
857 + const nextChildren = toArray(nextProps.render(nextValue));
858 + const frame: Frame = {
859 + type: nextChild,
860 + domNamespace: parentNamespace,
861 + children: nextChildren,
862 + childIndex: 0,
863 + context: context,
864 + footer: '',
865 + };
866 + if (__DEV__) {
867 + ((frame: any): FrameDev).debugElementStack = [];
868 + }
869 + this.stack.push(frame);
870 + return '';
871 + }
872 + default:
873 + break;
874 + }
875 + }
876 + invariant(
877 + false,
878 + 'Element type is invalid: expected a string (for built-in ' +
879 + 'components) or a class/function (for composite components) ' +
880 + 'but got: %s.',
881 + elementType == null ? elementType : typeof elementType,
882 + );
883 }
884 }
885
@@ -1052,7 +1166,7 @@ class ReactDOMServerRenderer {
1166 }
1167 const frame = {
1168 domNamespace: getChildNamespace(parentNamespace, element.type),
1055 - tag,
1169 + type: tag,
1170 children,
1171 childIndex: 0,
1172 context: context,
packages/react-reconciler/src/ReactFiber.js
+76 -77
@@ -7,7 +7,7 @@
7 */
8
9 import type {ReactElement, Source} from 'shared/ReactElementType';
10 -import type {ReactFragment, ReactPortal} from 'shared/ReactTypes';
10 +import type {ReactPortal} from 'shared/ReactTypes';
11 import type {TypeOfWork} from 'shared/ReactTypeOfWork';
12 import type {TypeOfInternalContext} from './ReactTypeOfInternalContext';
13 import type {TypeOfSideEffect} from 'shared/ReactTypeOfSideEffect';
@@ -27,6 +27,8 @@ import {
27 ReturnComponent,
28 Fragment,
29 Mode,
30 + ContextProvider,
31 + ContextConsumer,
32 } from 'shared/ReactTypeOfWork';
33 import getComponentName from 'shared/getComponentName';
34
@@ -41,6 +43,8 @@ import {
43 REACT_RETURN_TYPE,
44 REACT_CALL_TYPE,
45 REACT_STRICT_MODE_TYPE,
46 + REACT_PROVIDER_TYPE,
47 + REACT_CONTEXT_TYPE,
48 } from 'shared/ReactSymbols';
49
50 let hasBadMapPolyfill;
@@ -316,20 +320,13 @@ export function createFiberFromElement(
320 let fiber;
321 const type = element.type;
322 const key = element.key;
319 - const pendingProps = element.props;
323 + let pendingProps = element.props;
324 +
325 + let fiberTag;
326 if (typeof type === 'function') {
321 - fiber = shouldConstruct(type)
322 - ? createFiber(ClassComponent, pendingProps, key, internalContextTag)
323 - : createFiber(
324 - IndeterminateComponent,
325 - pendingProps,
326 - key,
327 - internalContextTag,
328 - );
329 - fiber.type = type;
327 + fiberTag = shouldConstruct(type) ? ClassComponent : IndeterminateComponent;
328 } else if (typeof type === 'string') {
331 - fiber = createFiber(HostComponent, pendingProps, key, internalContextTag);
332 - fiber.type = type;
329 + fiberTag = HostComponent;
330 } else {
331 switch (type) {
332 case REACT_FRAGMENT_TYPE:
@@ -340,90 +337,92 @@ export function createFiberFromElement(
337 key,
338 );
339 case REACT_STRICT_MODE_TYPE:
343 - fiber = createFiber(
344 - Mode,
345 - pendingProps,
346 - key,
347 - internalContextTag | StrictMode,
348 - );
349 - fiber.type = REACT_STRICT_MODE_TYPE;
340 + fiberTag = Mode;
341 + internalContextTag |= StrictMode;
342 break;
343 case REACT_CALL_TYPE:
352 - fiber = createFiber(
353 - CallComponent,
354 - pendingProps,
355 - key,
356 - internalContextTag,
357 - );
358 - fiber.type = REACT_CALL_TYPE;
344 + fiberTag = CallComponent;
345 break;
346 case REACT_RETURN_TYPE:
361 - fiber = createFiber(
362 - ReturnComponent,
363 - pendingProps,
364 - key,
365 - internalContextTag,
366 - );
367 - fiber.type = REACT_RETURN_TYPE;
347 + fiberTag = ReturnComponent;
348 break;
349 default: {
370 - if (
371 - typeof type === 'object' &&
372 - type !== null &&
373 - typeof type.tag === 'number'
374 - ) {
375 - // Currently assumed to be a continuation and therefore is a
376 - // fiber already.
377 - // TODO: The yield system is currently broken for updates in some
378 - // cases. The reified yield stores a fiber, but we don't know which
379 - // fiber that is; the current or a workInProgress? When the
380 - // continuation gets rendered here we don't know if we can reuse that
381 - // fiber or if we need to clone it. There is probably a clever way to
382 - // restructure this.
383 - fiber = ((type: any): Fiber);
384 - fiber.pendingProps = pendingProps;
385 - } else {
386 - let info = '';
387 - if (__DEV__) {
388 - if (
389 - type === undefined ||
390 - (typeof type === 'object' &&
391 - type !== null &&
392 - Object.keys(type).length === 0)
393 - ) {
394 - info +=
395 - ' You likely forgot to export your component from the file ' +
396 - "it's defined in, or you might have mixed up default and " +
397 - 'named imports.';
398 - }
399 - const ownerName = owner ? getComponentName(owner) : null;
400 - if (ownerName) {
401 - info += '\n\nCheck the render method of `' + ownerName + '`.';
402 - }
350 + if (typeof type === 'object' && type !== null) {
351 + switch (type.$$typeof) {
352 + case REACT_PROVIDER_TYPE:
353 + fiberTag = ContextProvider;
354 + break;
355 + case REACT_CONTEXT_TYPE:
356 + // This is a consumer
357 + fiberTag = ContextConsumer;
358 + break;
359 + default:
360 + if (typeof type.tag === 'number') {
361 + // Currently assumed to be a continuation and therefore is a
362 + // fiber already.
363 + // TODO: The yield system is currently broken for updates in
364 + // some cases. The reified yield stores a fiber, but we don't
365 + // know which fiber that is; the current or a workInProgress?
366 + // When the continuation gets rendered here we don't know if we
367 + // can reuse that fiber or if we need to clone it. There is
368 + // probably a clever way to restructure this.
369 + fiber = ((type: any): Fiber);
370 + fiber.pendingProps = pendingProps;
371 + fiber.expirationTime = expirationTime;
372 + return fiber;
373 + } else {
374 + throwOnInvalidElementType(type, owner);
375 + }
376 + break;
377 }
404 - invariant(
405 - false,
406 - 'Element type is invalid: expected a string (for built-in ' +
407 - 'components) or a class/function (for composite components) ' +
408 - 'but got: %s.%s',
409 - type == null ? type : typeof type,
410 - info,
411 - );
378 + } else {
379 + throwOnInvalidElementType(type, owner);
380 }
381 }
382 }
383 }
384
385 + fiber = createFiber(fiberTag, pendingProps, key, internalContextTag);
386 + fiber.type = type;
387 + fiber.expirationTime = expirationTime;
388 +
389 if (__DEV__) {
390 fiber._debugSource = element._source;
391 fiber._debugOwner = element._owner;
392 }
393
422 - fiber.expirationTime = expirationTime;
423 -
394 return fiber;
395 }
396
397 +function throwOnInvalidElementType(type, owner) {
398 + let info = '';
399 + if (__DEV__) {
400 + if (
401 + type === undefined ||
402 + (typeof type === 'object' &&
403 + type !== null &&
404 + Object.keys(type).length === 0)
405 + ) {
406 + info +=
407 + ' You likely forgot to export your component from the file ' +
408 + "it's defined in, or you might have mixed up default and " +
409 + 'named imports.';
410 + }
411 + const ownerName = owner ? getComponentName(owner) : null;
412 + if (ownerName) {
413 + info += '\n\nCheck the render method of `' + ownerName + '`.';
414 + }
415 + }
416 + invariant(
417 + false,
418 + 'Element type is invalid: expected a string (for built-in ' +
419 + 'components) or a class/function (for composite components) ' +
420 + 'but got: %s.%s',
421 + type == null ? type : typeof type,
422 + info,
423 + );
424 +}
425 +
426 export function createFiberFromFragment(
427 elements: ReactFragment,
428 internalContextTag: TypeOfInternalContext,
packages/react-reconciler/src/ReactFiberBeginWork.js
+245 -17
@@ -8,12 +8,14 @@
8 */
9
10 import type {HostConfig} from 'react-reconciler';
11 +import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
12 import type {Fiber} from 'react-reconciler/src/ReactFiber';
13 import type {HostContext} from './ReactFiberHostContext';
14 import type {HydrationContext} from './ReactFiberHydrationContext';
15 import type {FiberRoot} from './ReactFiberRoot';
16 import type {ExpirationTime} from './ReactFiberExpirationTime';
17
18 +import {enableNewContextAPI} from 'shared/ReactFeatureFlags';
19 import {
20 IndeterminateComponent,
21 FunctionalComponent,
@@ -27,6 +29,8 @@ import {
29 ReturnComponent,
30 Fragment,
31 Mode,
32 + ContextProvider,
33 + ContextConsumer,
34 } from 'shared/ReactTypeOfWork';
35 import {
36 PerformedWork,
@@ -53,13 +57,15 @@ import {processUpdateQueue} from './ReactFiberUpdateQueue';
57 import {
58 getMaskedContext,
59 getUnmaskedContext,
56 - hasContextChanged,
57 - pushContextProvider,
60 + hasContextChanged as hasLegacyContextChanged,
61 + pushContextProvider as pushLegacyContextProvider,
62 pushTopLevelContextObject,
63 invalidateContextProvider,
64 } from './ReactFiberContext';
65 +import {pushProvider} from './ReactFiberNewContext';
66 import {NoWork, Never} from './ReactFiberExpirationTime';
67 import {AsyncUpdates} from './ReactTypeOfInternalContext';
68 +import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
69
70 let didWarnAboutBadClass;
71 let didWarnAboutGetDerivedStateOnFunctionalComponent;
@@ -147,13 +153,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
153
154 function updateFragment(current, workInProgress) {
155 const nextChildren = workInProgress.pendingProps;
150 - if (hasContextChanged()) {
156 + if (hasLegacyContextChanged()) {
157 // Normally we can bail out on props equality but if context has changed
158 // we don't do the bailout and we have to reuse existing props instead.
153 - } else if (
154 - nextChildren === null ||
155 - workInProgress.memoizedProps === nextChildren
156 - ) {
159 + } else if (workInProgress.memoizedProps === nextChildren) {
160 return bailoutOnAlreadyFinishedWork(current, workInProgress);
161 }
162 reconcileChildren(current, workInProgress, nextChildren);
@@ -163,7 +166,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
166
167 function updateMode(current, workInProgress) {
168 const nextChildren = workInProgress.pendingProps.children;
166 - if (hasContextChanged()) {
169 + if (hasLegacyContextChanged()) {
170 // Normally we can bail out on props equality but if context has changed
171 // we don't do the bailout and we have to reuse existing props instead.
172 } else if (
@@ -189,7 +192,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
192 const fn = workInProgress.type;
193 const nextProps = workInProgress.pendingProps;
194
192 - if (hasContextChanged()) {
195 + if (hasLegacyContextChanged()) {
196 // Normally we can bail out on props equality but if context has changed
197 // we don't do the bailout and we have to reuse existing props instead.
198 } else {
@@ -228,7 +231,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
231 // Push context providers early to prevent context stack mismatches.
232 // During mounting we don't know the child context yet as the instance doesn't exist.
233 // We will invalidate the child context in finishClassComponent() right after rendering.
231 - const hasContext = pushContextProvider(workInProgress);
234 + const hasContext = pushLegacyContextProvider(workInProgress);
235
236 let shouldUpdate;
237 if (current === null) {
@@ -397,7 +400,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
400 const nextProps = workInProgress.pendingProps;
401 const prevProps = current !== null ? current.memoizedProps : null;
402
400 - if (hasContextChanged()) {
403 + if (hasLegacyContextChanged()) {
404 // Normally we can bail out on props equality but if context has changed
405 // we don't do the bailout and we have to reuse existing props instead.
406 } else if (memoizedProps === nextProps) {
@@ -492,7 +495,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
495 if (
496 typeof value === 'object' &&
497 value !== null &&
495 - typeof value.render === 'function'
498 + typeof value.render === 'function' &&
499 + value.$$typeof === undefined
500 ) {
501 const Component = workInProgress.type;
502
@@ -521,7 +525,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
525 // Push context providers early to prevent context stack mismatches.
526 // During mounting we don't know the child context yet as the instance doesn't exist.
527 // We will invalidate the child context in finishClassComponent() right after rendering.
524 - const hasContext = pushContextProvider(workInProgress);
528 + const hasContext = pushLegacyContextProvider(workInProgress);
529 adoptClassInstance(workInProgress, value);
530 mountClassInstance(workInProgress, renderExpirationTime);
531 return finishClassComponent(current, workInProgress, true, hasContext);
@@ -587,7 +591,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
591
592 function updateCallComponent(current, workInProgress, renderExpirationTime) {
593 let nextProps = workInProgress.pendingProps;
590 - if (hasContextChanged()) {
594 + if (hasLegacyContextChanged()) {
595 // Normally we can bail out on props equality but if context has changed
596 // we don't do the bailout and we have to reuse existing props instead.
597 } else if (workInProgress.memoizedProps === nextProps) {
@@ -630,7 +634,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
634 ) {
635 pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
636 const nextChildren = workInProgress.pendingProps;
633 - if (hasContextChanged()) {
637 + if (hasLegacyContextChanged()) {
638 // Normally we can bail out on props equality but if context has changed
639 // we don't do the bailout and we have to reuse existing props instead.
640 } else if (workInProgress.memoizedProps === nextChildren) {
@@ -657,6 +661,215 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
661 return workInProgress.child;
662 }
663
664 + function propagateContextChange<V>(
665 + workInProgress: Fiber,
666 + context: ReactContext<V>,
667 + changedBits: number,
668 + renderExpirationTime: ExpirationTime,
669 + ): void {
670 + if (enableNewContextAPI) {
671 + let fiber = workInProgress.child;
672 + while (fiber !== null) {
673 + let nextFiber;
674 + // Visit this fiber.
675 + switch (fiber.tag) {
676 + case ContextConsumer:
677 + // Check if the context matches.
678 + const observedBits: number = fiber.stateNode | 0;
679 + if (fiber.type === context && (observedBits & changedBits) !== 0) {
680 + // Update the expiration time of all the ancestors, including
681 + // the alternates.
682 + let node = fiber;
683 + while (node !== null) {
684 + const alternate = node.alternate;
685 + if (
686 + node.expirationTime === NoWork ||
687 + node.expirationTime > renderExpirationTime
688 + ) {
689 + node.expirationTime = renderExpirationTime;
690 + if (
691 + alternate !== null &&
692 + (alternate.expirationTime === NoWork ||
693 + alternate.expirationTime > renderExpirationTime)
694 + ) {
695 + alternate.expirationTime = renderExpirationTime;
696 + }
697 + } else if (
698 + alternate !== null &&
699 + (alternate.expirationTime === NoWork ||
700 + alternate.expirationTime > renderExpirationTime)
701 + ) {
702 + alternate.expirationTime = renderExpirationTime;
703 + } else {
704 + // Neither alternate was updated, which means the rest of the
705 + // ancestor path already has sufficient priority.
706 + break;
707 + }
708 + node = node.return;
709 + }
710 + // Don't scan deeper than a matching consumer. When we render the
711 + // consumer, we'll continue scanning from that point. This way the
712 + // scanning work is time-sliced.
713 + nextFiber = null;
714 + } else {
715 + // Traverse down.
716 + nextFiber = fiber.child;
717 + }
718 + break;
719 + case ContextProvider:
720 + // Don't scan deeper if this is a matching provider
721 + nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
722 + break;
723 + default:
724 + // Traverse down.
725 + nextFiber = fiber.child;
726 + break;
727 + }
728 + if (nextFiber !== null) {
729 + // Set the return pointer of the child to the work-in-progress fiber.
730 + nextFiber.return = fiber;
731 + } else {
732 + // No child. Traverse to next sibling.
733 + nextFiber = fiber;
734 + while (nextFiber !== null) {
735 + if (nextFiber === workInProgress) {
736 + // We're back to the root of this subtree. Exit.
737 + nextFiber = null;
738 + break;
739 + }
740 + let sibling = nextFiber.sibling;
741 + if (sibling !== null) {
742 + nextFiber = sibling;
743 + break;
744 + }
745 + // No more siblings. Traverse up.
746 + nextFiber = nextFiber.return;
747 + }
748 + }
749 + fiber = nextFiber;
750 + }
751 + }
752 + }
753 +
754 + function updateContextProvider(
755 + current,
756 + workInProgress,
757 + renderExpirationTime,
758 + ) {
759 + if (enableNewContextAPI) {
760 + const providerType: ReactProviderType<any> = workInProgress.type;
761 + const context: ReactContext<any> = providerType.context;
762 +
763 + const newProps = workInProgress.pendingProps;
764 + const oldProps = workInProgress.memoizedProps;
765 +
766 + if (hasLegacyContextChanged()) {
767 + // Normally we can bail out on props equality but if context has changed
768 + // we don't do the bailout and we have to reuse existing props instead.
769 + } else if (oldProps === newProps) {
770 + workInProgress.stateNode = 0;
771 + pushProvider(workInProgress);
772 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
773 + }
774 + workInProgress.memoizedProps = newProps;
775 +
776 + const newValue = newProps.value;
777 +
778 + let changedBits: number;
779 + if (oldProps === null) {
780 + // Initial render
781 + changedBits = MAX_SIGNED_31_BIT_INT;
782 + } else {
783 + const oldValue = oldProps.value;
784 + // Use Object.is to compare the new context value to the old value.
785 + // Inlined Object.is polyfill.
786 + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
787 + if (
788 + (oldValue === newValue &&
789 + (oldValue !== 0 || 1 / oldValue === 1 / newValue)) ||
790 + (oldValue !== oldValue && newValue !== newValue) // eslint-disable-line no-self-compare
791 + ) {
792 + // No change.
793 + changedBits = 0;
794 + } else {
795 + changedBits =
796 + context.calculateChangedBits !== null
797 + ? context.calculateChangedBits(oldValue, newValue)
798 + : MAX_SIGNED_31_BIT_INT;
799 + if (__DEV__) {
800 + warning(
801 + (changedBits & MAX_SIGNED_31_BIT_INT) === changedBits,
802 + 'calculateChangedBits: Expected the return value to be a ' +
803 + '31-bit integer. Instead received: %s',
804 + changedBits,
805 + );
806 + }
807 + changedBits |= 0;
808 +
809 + if (changedBits !== 0) {
810 + propagateContextChange(
811 + workInProgress,
812 + context,
813 + changedBits,
814 + renderExpirationTime,
815 + );
816 + }
817 + }
818 + }
819 +
820 + workInProgress.stateNode = changedBits;
821 + pushProvider(workInProgress);
822 +
823 + if (oldProps !== null && oldProps.children === newProps.children) {
824 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
825 + }
826 + const newChildren = newProps.children;
827 + reconcileChildren(current, workInProgress, newChildren);
828 + return workInProgress.child;
829 + } else {
830 + return null;
831 + }
832 + }
833 +
834 + function updateContextConsumer(
835 + current,
836 + workInProgress,
837 + renderExpirationTime,
838 + ) {
839 + if (enableNewContextAPI) {
840 + const context: ReactContext<any> = workInProgress.type;
841 + const newProps = workInProgress.pendingProps;
842 +
843 + const newValue = context.currentValue;
844 + const changedBits = context.changedBits;
845 +
846 + if (changedBits !== 0) {
847 + // Context change propagation stops at matching consumers, for time-
848 + // slicing. Continue the propagation here.
849 + propagateContextChange(
850 + workInProgress,
851 + context,
852 + changedBits,
853 + renderExpirationTime,
854 + );
855 + }
856 +
857 + // Store the observedBits on the fiber's stateNode for quick access.
858 + let observedBits = newProps.observedBits;
859 + if (observedBits === undefined || observedBits === null) {
860 + // Subscribe to all changes by default
861 + observedBits = MAX_SIGNED_31_BIT_INT;
862 + }
863 + workInProgress.stateNode = observedBits;
864 +
865 + const newChildren = newProps.render(newValue);
866 + reconcileChildren(current, workInProgress, newChildren);
867 + return workInProgress.child;
868 + } else {
869 + return null;
870 + }
871 + }
872 +
873 /*
874 function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
875 let child = firstChild;
@@ -710,7 +923,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
923 pushHostRootContext(workInProgress);
924 break;
925 case ClassComponent:
713 - pushContextProvider(workInProgress);
926 + pushLegacyContextProvider(workInProgress);
927 break;
928 case HostPortal:
929 pushHostContainer(
@@ -718,6 +931,9 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
931 workInProgress.stateNode.containerInfo,
932 );
933 break;
934 + case ContextProvider:
935 + pushProvider(workInProgress);
936 + break;
937 }
938 // TODO: What if this is currently in progress?
939 // How can that happen? How is this not being cloned?
@@ -796,6 +1012,18 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
1012 return updateFragment(current, workInProgress);
1013 case Mode:
1014 return updateMode(current, workInProgress);
1015 + case ContextProvider:
1016 + return updateContextProvider(
1017 + current,
1018 + workInProgress,
1019 + renderExpirationTime,
1020 + );
1021 + case ContextConsumer:
1022 + return updateContextConsumer(
1023 + current,
1024 + workInProgress,
1025 + renderExpirationTime,
1026 + );
1027 default:
1028 invariant(
1029 false,
@@ -813,7 +1041,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
1041 // Push context providers here to avoid a push/pop context mismatch.
1042 switch (workInProgress.tag) {
1043 case ClassComponent:
816 - pushContextProvider(workInProgress);
1044 + pushLegacyContextProvider(workInProgress);
1045 break;
1046 case HostRoot:
1047 pushHostRootContext(workInProgress);
packages/react-reconciler/src/ReactFiberCompleteWork.js
+13 -4
@@ -30,6 +30,8 @@ import {
30 CallComponent,
31 CallHandlerPhase,
32 ReturnComponent,
33 + ContextProvider,
34 + ContextConsumer,
35 Fragment,
36 Mode,
37 } from 'shared/ReactTypeOfWork';
@@ -38,9 +40,10 @@ import invariant from 'fbjs/lib/invariant';
40
41 import {reconcileChildFibers} from './ReactChildFiber';
42 import {
41 - popContextProvider,
42 - popTopLevelContextObject,
43 + popContextProvider as popLegacyContextProvider,
44 + popTopLevelContextObject as popTopLevelLegacyContextObject,
45 } from './ReactFiberContext';
46 +import {popProvider} from './ReactFiberNewContext';
47
48 export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
49 config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
@@ -401,12 +404,12 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
404 return null;
405 case ClassComponent: {
406 // We are leaving this subtree, so pop context if any.
404 - popContextProvider(workInProgress);
407 + popLegacyContextProvider(workInProgress);
408 return null;
409 }
410 case HostRoot: {
411 popHostContainer(workInProgress);
409 - popTopLevelContextObject(workInProgress);
412 + popTopLevelLegacyContextObject(workInProgress);
413 const fiberRoot = (workInProgress.stateNode: FiberRoot);
414 if (fiberRoot.pendingContext) {
415 fiberRoot.context = fiberRoot.pendingContext;
@@ -583,6 +586,12 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
586 popHostContainer(workInProgress);
587 updateHostContainer(workInProgress);
588 return null;
589 + case ContextProvider:
590 + // Pop provider fiber
591 + popProvider(workInProgress);
592 + return null;
593 + case ContextConsumer:
594 + return null;
595 // Error cases
596 case IndeterminateComponent:
597 invariant(
packages/react-reconciler/src/ReactFiberExpirationTime.js
+3 -1
@@ -7,12 +7,14 @@
7 * @flow
8 */
9
10 +import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
11 +
12 // TODO: Use an opaque type once ESLint et al support the syntax
13 export type ExpirationTime = number;
14
15 export const NoWork = 0;
16 export const Sync = 1;
15 -export const Never = 2147483647; // Max int32: Math.pow(2, 31) - 1
17 +export const Never = MAX_SIGNED_31_BIT_INT;
18
19 const UNIT_SIZE = 10;
20 const MAGIC_NUMBER_OFFSET = 2;
packages/react-reconciler/src/ReactFiberNewContext.js new
+70
@@ -0,0 +1,70 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 {Fiber} from './ReactFiber';
11 +import type {ReactContext} from 'shared/ReactTypes';
12 +
13 +import warning from 'fbjs/lib/warning';
14 +
15 +let stack: Array<Fiber> = [];
16 +let index = -1;
17 +
18 +let rendererSigil;
19 +if (__DEV__) {
20 + // Use this to detect multiple renderers using the same context
21 + rendererSigil = {};
22 +}
23 +
24 +export function pushProvider(providerFiber: Fiber): void {
25 + index += 1;
26 + stack[index] = providerFiber;
27 + const context: ReactContext<any> = providerFiber.type.context;
28 + context.currentValue = providerFiber.pendingProps.value;
29 + context.changedBits = providerFiber.stateNode;
30 +
31 + if (__DEV__) {
32 + warning(
33 + context._currentRenderer === null ||
34 + context._currentRenderer === rendererSigil,
35 + 'Detected multiple renderers concurrently rendering the ' +
36 + 'same context provider. This is currently unsupported.',
37 + );
38 + context._currentRenderer = rendererSigil;
39 + }
40 +}
41 +
42 +export function popProvider(providerFiber: Fiber): void {
43 + if (__DEV__) {
44 + warning(index > -1 && providerFiber === stack[index], 'Unexpected pop.');
45 + }
46 + stack[index] = null;
47 + index -= 1;
48 + const context: ReactContext<any> = providerFiber.type.context;
49 + if (index < 0) {
50 + context.currentValue = context.defaultValue;
51 + context.changedBits = 0;
52 + } else {
53 + const previousProviderFiber = stack[index];
54 + context.currentValue = previousProviderFiber.pendingProps.value;
55 + context.changedBits = previousProviderFiber.stateNode;
56 + }
57 +}
58 +
59 +export function resetProviderStack(): void {
60 + for (let i = index; i > -1; i--) {
61 + const providerFiber = stack[i];
62 + const context: ReactContext<any> = providerFiber.type.context;
63 + context.currentValue = context.defaultValue;
64 + context.changedBits = 0;
65 + stack[i] = null;
66 + if (__DEV__) {
67 + context._currentRenderer = null;
68 + }
69 + }
70 +}
packages/react-reconciler/src/ReactFiberScheduler.js
+9 -2
@@ -33,6 +33,7 @@ import {
33 HostComponent,
34 HostPortal,
35 ClassComponent,
36 + ContextProvider,
37 } from 'shared/ReactTypeOfWork';
38 import {enableUserTimingAPI} from 'shared/ReactFeatureFlags';
39 import getComponentName from 'shared/getComponentName';
@@ -64,6 +65,7 @@ import {
65 stopCommitLifeCyclesTimer,
66 } from './ReactDebugFiberPerf';
67 import {popContextProvider} from './ReactFiberContext';
68 +import {popProvider} from './ReactFiberNewContext';
69 import {reset} from './ReactFiberStack';
70 import {logCapturedError} from './ReactFiberErrorLogger';
71 import {createWorkInProgress} from './ReactFiber';
@@ -78,7 +80,8 @@ import {
80 } from './ReactFiberExpirationTime';
81 import {AsyncUpdates} from './ReactTypeOfInternalContext';
82 import {getUpdateExpirationTime} from './ReactFiberUpdateQueue';
81 -import {resetContext} from './ReactFiberContext';
83 +import {resetContext as resetLegacyContext} from './ReactFiberContext';
84 +import {resetProviderStack} from './ReactFiberNewContext';
85
86 const {
87 invokeGuardedCallback,
@@ -238,7 +241,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
241 // Reset the stack
242 reset();
243 // Reset the cursors
241 - resetContext();
244 + resetLegacyContext();
245 + resetProviderStack();
246 resetHostContainer();
247 }
248
@@ -1134,6 +1138,9 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
1138 case HostPortal:
1139 popHostContainer(node);
1140 break;
1141 + case ContextProvider:
1142 + popProvider(node);
1143 + break;
1144 }
1145 if (node === to || node.alternate === to) {
1146 stopFailedWorkTimer(node);
packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js new
+784
@@ -0,0 +1,784 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +let ReactFeatureFlags = require('shared/ReactFeatureFlags');
13 +ReactFeatureFlags.enableNewContextAPI = true;
14 +
15 +let React = require('react');
16 +let ReactNoop;
17 +let gen;
18 +
19 +describe('ReactNewContext', () => {
20 + beforeEach(() => {
21 + jest.resetModules();
22 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
23 + ReactFeatureFlags.enableNewContextAPI = true;
24 + React = require('react');
25 + ReactNoop = require('react-noop-renderer');
26 + gen = require('random-seed');
27 + });
28 +
29 + // function div(...children) {
30 + // children = children.map(c => (typeof c === 'string' ? {text: c} : c));
31 + // return {type: 'div', children, prop: undefined};
32 + // }
33 +
34 + function span(prop) {
35 + return {type: 'span', children: [], prop};
36 + }
37 +
38 + it('simple mount and update', () => {
39 + const Context = React.unstable_createContext(1);
40 +
41 + function Provider(props) {
42 + return Context.provide(props.value, props.children);
43 + }
44 +
45 + function Consumer(props) {
46 + return Context.consume(value => {
47 + return <span prop={'Result: ' + value} />;
48 + });
49 + }
50 +
51 + const Indirection = React.Fragment;
52 +
53 + function App(props) {
54 + return (
55 + <Provider value={props.value}>
56 + <Indirection>
57 + <Indirection>
58 + <Consumer />
59 + </Indirection>
60 + </Indirection>
61 + </Provider>
62 + );
63 + }
64 +
65 + ReactNoop.render(<App value={2} />);
66 + ReactNoop.flush();
67 + expect(ReactNoop.getChildren()).toEqual([span('Result: 2')]);
68 +
69 + // Update
70 + ReactNoop.render(<App value={3} />);
71 + ReactNoop.flush();
72 + expect(ReactNoop.getChildren()).toEqual([span('Result: 3')]);
73 + });
74 +
75 + it('propagates through shouldComponentUpdate false', () => {
76 + const Context = React.unstable_createContext(1);
77 +
78 + function Provider(props) {
79 + ReactNoop.yield('Provider');
80 + return Context.provide(props.value, props.children);
81 + }
82 +
83 + function Consumer(props) {
84 + ReactNoop.yield('Consumer');
85 + return Context.consume(value => {
86 + ReactNoop.yield('Consumer render prop');
87 + return <span prop={'Result: ' + value} />;
88 + });
89 + }
90 +
91 + class Indirection extends React.Component {
92 + shouldComponentUpdate() {
93 + return false;
94 + }
95 + render() {
96 + ReactNoop.yield('Indirection');
97 + return this.props.children;
98 + }
99 + }
100 +
101 + function App(props) {
102 + ReactNoop.yield('App');
103 + return (
104 + <Provider value={props.value}>
105 + <Indirection>
106 + <Indirection>
107 + <Consumer />
108 + </Indirection>
109 + </Indirection>
110 + </Provider>
111 + );
112 + }
113 +
114 + ReactNoop.render(<App value={2} />);
115 + expect(ReactNoop.flush()).toEqual([
116 + 'App',
117 + 'Provider',
118 + 'Indirection',
119 + 'Indirection',
120 + 'Consumer',
121 + 'Consumer render prop',
122 + ]);
123 + expect(ReactNoop.getChildren()).toEqual([span('Result: 2')]);
124 +
125 + // Update
126 + ReactNoop.render(<App value={3} />);
127 + expect(ReactNoop.flush()).toEqual([
128 + 'App',
129 + 'Provider',
130 + 'Consumer render prop',
131 + ]);
132 + expect(ReactNoop.getChildren()).toEqual([span('Result: 3')]);
133 + });
134 +
135 + it('consumers bail out if context value is the same', () => {
136 + const Context = React.unstable_createContext(1);
137 +
138 + function Provider(props) {
139 + ReactNoop.yield('Provider');
140 + return Context.provide(props.value, props.children);
141 + }
142 +
143 + function Consumer(props) {
144 + ReactNoop.yield('Consumer');
145 + return Context.consume(value => {
146 + ReactNoop.yield('Consumer render prop');
147 + return <span prop={'Result: ' + value} />;
148 + });
149 + }
150 +
151 + class Indirection extends React.Component {
152 + shouldComponentUpdate() {
153 + return false;
154 + }
155 + render() {
156 + ReactNoop.yield('Indirection');
157 + return this.props.children;
158 + }
159 + }
160 +
161 + function App(props) {
162 + ReactNoop.yield('App');
163 + return (
164 + <Provider value={props.value}>
165 + <Indirection>
166 + <Indirection>
167 + <Consumer />
168 + </Indirection>
169 + </Indirection>
170 + </Provider>
171 + );
172 + }
173 +
174 + ReactNoop.render(<App value={2} />);
175 + expect(ReactNoop.flush()).toEqual([
176 + 'App',
177 + 'Provider',
178 + 'Indirection',
179 + 'Indirection',
180 + 'Consumer',
181 + 'Consumer render prop',
182 + ]);
183 + expect(ReactNoop.getChildren()).toEqual([span('Result: 2')]);
184 +
185 + // Update with the same context value
186 + ReactNoop.render(<App value={2} />);
187 + expect(ReactNoop.flush()).toEqual([
188 + 'App',
189 + 'Provider',
190 + // Don't call render prop again
191 + ]);
192 + expect(ReactNoop.getChildren()).toEqual([span('Result: 2')]);
193 + });
194 +
195 + it('nested providers', () => {
196 + const Context = React.unstable_createContext(1);
197 +
198 + function Provider(props) {
199 + return Context.consume(contextValue =>
200 + // Multiply previous context value by 2, unless prop overrides
201 + Context.provide(props.value || contextValue * 2, props.children),
202 + );
203 + }
204 +
205 + function Consumer(props) {
206 + return Context.consume(value => {
207 + return <span prop={'Result: ' + value} />;
208 + });
209 + }
210 +
211 + class Indirection extends React.Component {
212 + shouldComponentUpdate() {
213 + return false;
214 + }
215 + render() {
216 + return this.props.children;
217 + }
218 + }
219 +
220 + function App(props) {
221 + return (
222 + <Provider value={props.value}>
223 + <Indirection>
224 + <Provider>
225 + <Indirection>
226 + <Provider>
227 + <Indirection>
228 + <Consumer />
229 + </Indirection>
230 + </Provider>
231 + </Indirection>
232 + </Provider>
233 + </Indirection>
234 + </Provider>
235 + );
236 + }
237 +
238 + ReactNoop.render(<App value={2} />);
239 + ReactNoop.flush();
240 + expect(ReactNoop.getChildren()).toEqual([span('Result: 8')]);
241 +
242 + // Update
243 + ReactNoop.render(<App value={3} />);
244 + ReactNoop.flush();
245 + expect(ReactNoop.getChildren()).toEqual([span('Result: 12')]);
246 + });
247 +
248 + it('multiple consumers in different branches', () => {
249 + const Context = React.unstable_createContext(1);
250 +
251 + function Provider(props) {
252 + return Context.consume(contextValue =>
253 + // Multiply previous context value by 2, unless prop overrides
254 + Context.provide(props.value || contextValue * 2, props.children),
255 + );
256 + }
257 +
258 + function Consumer(props) {
259 + return Context.consume(value => {
260 + return <span prop={'Result: ' + value} />;
261 + });
262 + }
263 +
264 + class Indirection extends React.Component {
265 + shouldComponentUpdate() {
266 + return false;
267 + }
268 + render() {
269 + return this.props.children;
270 + }
271 + }
272 +
273 + function App(props) {
274 + return (
275 + <Provider value={props.value}>
276 + <Indirection>
277 + <Indirection>
278 + <Provider>
279 + <Consumer />
280 + </Provider>
281 + </Indirection>
282 + <Indirection>
283 + <Consumer />
284 + </Indirection>
285 + </Indirection>
286 + </Provider>
287 + );
288 + }
289 +
290 + ReactNoop.render(<App value={2} />);
291 + ReactNoop.flush();
292 + expect(ReactNoop.getChildren()).toEqual([
293 + span('Result: 4'),
294 + span('Result: 2'),
295 + ]);
296 +
297 + // Update
298 + ReactNoop.render(<App value={3} />);
299 + ReactNoop.flush();
300 + expect(ReactNoop.getChildren()).toEqual([
301 + span('Result: 6'),
302 + span('Result: 3'),
303 + ]);
304 +
305 + // Another update
306 + ReactNoop.render(<App value={4} />);
307 + ReactNoop.flush();
308 + expect(ReactNoop.getChildren()).toEqual([
309 + span('Result: 8'),
310 + span('Result: 4'),
311 + ]);
312 + });
313 +
314 + it('compares context values with Object.is semantics', () => {
315 + const Context = React.unstable_createContext(1);
316 +
317 + function Provider(props) {
318 + ReactNoop.yield('Provider');
319 + return Context.provide(props.value, props.children);
320 + }
321 +
322 + function Consumer(props) {
323 + ReactNoop.yield('Consumer');
324 + return Context.consume(value => {
325 + ReactNoop.yield('Consumer render prop');
326 + return <span prop={'Result: ' + value} />;
327 + });
328 + }
329 +
330 + class Indirection extends React.Component {
331 + shouldComponentUpdate() {
332 + return false;
333 + }
334 + render() {
335 + ReactNoop.yield('Indirection');
336 + return this.props.children;
337 + }
338 + }
339 +
340 + function App(props) {
341 + ReactNoop.yield('App');
342 + return (
343 + <Provider value={props.value}>
344 + <Indirection>
345 + <Indirection>
346 + <Consumer />
347 + </Indirection>
348 + </Indirection>
349 + </Provider>
350 + );
351 + }
352 +
353 + ReactNoop.render(<App value={NaN} />);
354 + expect(ReactNoop.flush()).toEqual([
355 + 'App',
356 + 'Provider',
357 + 'Indirection',
358 + 'Indirection',
359 + 'Consumer',
360 + 'Consumer render prop',
361 + ]);
362 + expect(ReactNoop.getChildren()).toEqual([span('Result: NaN')]);
363 +
364 + // Update
365 + ReactNoop.render(<App value={NaN} />);
366 + expect(ReactNoop.flush()).toEqual([
367 + 'App',
368 + 'Provider',
369 + // Consumer should not re-render again
370 + // 'Consumer render prop',
371 + ]);
372 + expect(ReactNoop.getChildren()).toEqual([span('Result: NaN')]);
373 + });
374 +
375 + it('context unwinds when interrupted', () => {
376 + const Context = React.unstable_createContext('Default');
377 +
378 + function Provider(props) {
379 + return Context.provide(props.value, props.children);
380 + }
381 +
382 + function Consumer(props) {
383 + return Context.consume(value => {
384 + return <span prop={'Result: ' + value} />;
385 + });
386 + }
387 +
388 + function BadRender() {
389 + throw new Error('Bad render');
390 + }
391 +
392 + class ErrorBoundary extends React.Component {
393 + state = {error: null};
394 + componentDidCatch(error) {
395 + this.setState({error});
396 + }
397 + render() {
398 + if (this.state.error) {
399 + return null;
400 + }
401 + return this.props.children;
402 + }
403 + }
404 +
405 + function App(props) {
406 + return (
407 + <React.Fragment>
408 + <Provider value="Does not unwind">
409 + <ErrorBoundary>
410 + <Provider value="Unwinds after BadRender throws">
411 + <BadRender />
412 + </Provider>
413 + </ErrorBoundary>
414 + <Consumer />
415 + </Provider>
416 + </React.Fragment>
417 + );
418 + }
419 +
420 + ReactNoop.render(<App value="A" />);
421 + ReactNoop.flush();
422 + expect(ReactNoop.getChildren()).toEqual([
423 + // The second provider should use the default value. This proves the
424 + span('Result: Does not unwind'),
425 + ]);
426 + });
427 +
428 + it('can skip consumers with bitmask', () => {
429 + const Context = React.unstable_createContext({foo: 0, bar: 0}, (a, b) => {
430 + let result = 0;
431 + if (a.foo !== b.foo) {
432 + result |= 0b01;
433 + }
434 + if (a.bar !== b.bar) {
435 + result |= 0b10;
436 + }
437 + return result;
438 + });
439 +
440 + function Provider(props) {
441 + return Context.provide({foo: props.foo, bar: props.bar}, props.children);
442 + }
443 +
444 + function Foo() {
445 + return Context.consume(value => {
446 + ReactNoop.yield('Foo');
447 + return <span prop={'Foo: ' + value.foo} />;
448 + }, 0b01);
449 + }
450 +
451 + function Bar() {
452 + return Context.consume(value => {
453 + ReactNoop.yield('Bar');
454 + return <span prop={'Bar: ' + value.bar} />;
455 + }, 0b10);
456 + }
457 +
458 + class Indirection extends React.Component {
459 + shouldComponentUpdate() {
460 + return false;
461 + }
462 + render() {
463 + return this.props.children;
464 + }
465 + }
466 +
467 + function App(props) {
468 + return (
469 + <Provider foo={props.foo} bar={props.bar}>
470 + <Indirection>
471 + <Indirection>
472 + <Foo />
473 + </Indirection>
474 + <Indirection>
475 + <Bar />
476 + </Indirection>
477 + </Indirection>
478 + </Provider>
479 + );
480 + }
481 +
482 + ReactNoop.render(<App foo={1} bar={1} />);
483 + expect(ReactNoop.flush()).toEqual(['Foo', 'Bar']);
484 + expect(ReactNoop.getChildren()).toEqual([span('Foo: 1'), span('Bar: 1')]);
485 +
486 + // Update only foo
487 + ReactNoop.render(<App foo={2} bar={1} />);
488 + expect(ReactNoop.flush()).toEqual(['Foo']);
489 + expect(ReactNoop.getChildren()).toEqual([span('Foo: 2'), span('Bar: 1')]);
490 +
491 + // Update only bar
492 + ReactNoop.render(<App foo={2} bar={2} />);
493 + expect(ReactNoop.flush()).toEqual(['Bar']);
494 + expect(ReactNoop.getChildren()).toEqual([span('Foo: 2'), span('Bar: 2')]);
495 +
496 + // Update both
497 + ReactNoop.render(<App foo={3} bar={3} />);
498 + expect(ReactNoop.flush()).toEqual(['Foo', 'Bar']);
499 + expect(ReactNoop.getChildren()).toEqual([span('Foo: 3'), span('Bar: 3')]);
500 + });
501 +
502 + it('warns if calculateChangedBits returns larger than a 31-bit integer', () => {
503 + spyOnDev(console, 'error');
504 +
505 + const Context = React.unstable_createContext(
506 + 0,
507 + (a, b) => Math.pow(2, 32) - 1, // Return 32 bit int
508 + );
509 +
510 + function Provider(props) {
511 + return Context.provide(props.value, props.children);
512 + }
513 +
514 + ReactNoop.render(<Provider value={1} />);
515 + ReactNoop.flush();
516 +
517 + // Update
518 + ReactNoop.render(<Provider value={2} />);
519 + ReactNoop.flush();
520 +
521 + if (__DEV__) {
522 + expect(console.error.calls.count()).toBe(1);
523 + expect(console.error.calls.argsFor(0)[0]).toContain(
524 + 'calculateChangedBits: Expected the return value to be a 31-bit ' +
525 + 'integer. Instead received: 4294967295',
526 + );
527 + }
528 + });
529 +
530 + it('warns if multiple renderers concurrently render the same context', () => {
531 + spyOnDev(console, 'error');
532 + const Context = React.unstable_createContext(0);
533 +
534 + function Foo(props) {
535 + ReactNoop.yield('Foo');
536 + return null;
537 + }
538 + function Provider(props) {
539 + return Context.provide(props.value, props.children);
540 + }
541 +
542 + function App(props) {
543 + return (
544 + <Provider value={props.value}>
545 + <Foo />
546 + <Foo />
547 + </Provider>
548 + );
549 + }
550 +
551 + ReactNoop.render(<App value={1} />);
552 + // Render past the Provider, but don't commit yet
553 + ReactNoop.flushThrough(['Foo']);
554 +
555 + // Get a new copy of ReactNoop
556 + jest.resetModules();
557 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
558 + ReactFeatureFlags.enableNewContextAPI = true;
559 + React = require('react');
560 + ReactNoop = require('react-noop-renderer');
561 +
562 + // Render the provider again using a different renderer
563 + ReactNoop.render(<App value={1} />);
564 + ReactNoop.flush();
565 +
566 + if (__DEV__) {
567 + expect(console.error.calls.argsFor(0)[0]).toContain(
568 + 'Detected multiple renderers concurrently rendering the same ' +
569 + 'context provider. This is currently unsupported',
570 + );
571 + }
572 + });
573 +
574 + describe('fuzz test', () => {
575 + const Fragment = React.Fragment;
576 + const contextKeys = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
577 +
578 + const FLUSH_ALL = 'FLUSH_ALL';
579 + function flushAll() {
580 + return {
581 + type: FLUSH_ALL,
582 + toString() {
583 + return `flushAll()`;
584 + },
585 + };
586 + }
587 +
588 + const FLUSH = 'FLUSH';
589 + function flush(unitsOfWork) {
590 + return {
591 + type: FLUSH,
592 + unitsOfWork,
593 + toString() {
594 + return `flush(${unitsOfWork})`;
595 + },
596 + };
597 + }
598 +
599 + const UPDATE = 'UPDATE';
600 + function update(key, value) {
601 + return {
602 + type: UPDATE,
603 + key,
604 + value,
605 + toString() {
606 + return `update('${key}', ${value})`;
607 + },
608 + };
609 + }
610 +
611 + function randomInteger(min, max) {
612 + min = Math.ceil(min);
613 + max = Math.floor(max);
614 + return Math.floor(Math.random() * (max - min)) + min;
615 + }
616 +
617 + function randomAction() {
618 + switch (randomInteger(0, 3)) {
619 + case 0:
620 + return flushAll();
621 + case 1:
622 + return flush(randomInteger(0, 500));
623 + case 2:
624 + const key = contextKeys[randomInteger(0, contextKeys.length)];
625 + const value = randomInteger(1, 10);
626 + return update(key, value);
627 + default:
628 + throw new Error('Switch statement should be exhaustive');
629 + }
630 + }
631 +
632 + function randomActions(n) {
633 + let actions = [];
634 + for (let i = 0; i < n; i++) {
635 + actions.push(randomAction());
636 + }
637 + return actions;
638 + }
639 +
640 + function ContextSimulator(maxDepth) {
641 + const contexts = new Map(
642 + contextKeys.map(key => {
643 + const Context = React.unstable_createContext(0);
644 + Context.displayName = 'Context' + key;
645 + return [key, Context];
646 + }),
647 + );
648 +
649 + class ConsumerTree extends React.Component {
650 + shouldComponentUpdate() {
651 + return false;
652 + }
653 + render() {
654 + if (this.props.depth >= this.props.maxDepth) {
655 + return null;
656 + }
657 + const consumers = [0, 1, 2].map(i => {
658 + const randomKey =
659 + contextKeys[
660 + this.props.rand.intBetween(0, contextKeys.length - 1)
661 + ];
662 + const Context = contexts.get(randomKey);
663 + return Context.consume(
664 + value => (
665 + <Fragment>
666 + <span prop={`${randomKey}:${value}`} />
667 + <ConsumerTree
668 + rand={this.props.rand}
669 + depth={this.props.depth + 1}
670 + maxDepth={this.props.maxDepth}
671 + />
672 + </Fragment>
673 + ),
674 + null,
675 + i,
676 + );
677 + });
678 + return consumers;
679 + }
680 + }
681 +
682 + function Root(props) {
683 + return contextKeys.reduceRight((children, key) => {
684 + const Context = contexts.get(key);
685 + const value = props.values[key];
686 + return Context.provide(value, children);
687 + }, <ConsumerTree rand={props.rand} depth={0} maxDepth={props.maxDepth} />);
688 + }
689 +
690 + const initialValues = contextKeys.reduce(
691 + (result, key, i) => ({...result, [key]: i + 1}),
692 + {},
693 + );
694 +
695 + function assertConsistentTree(expectedValues = {}) {
696 + const children = ReactNoop.getChildren();
697 + children.forEach(child => {
698 + const text = child.prop;
699 + const key = text[0];
700 + const value = parseInt(text[2], 10);
701 + const expectedValue = expectedValues[key];
702 + if (expectedValue === undefined) {
703 + // If an expected value was not explicitly passed to this function,
704 + // use the first occurrence.
705 + expectedValues[key] = value;
706 + } else if (value !== expectedValue) {
707 + throw new Error(
708 + `Inconsistent value! Expected: ${key}:${expectedValue}. Actual: ${
709 + text
710 + }`,
711 + );
712 + }
713 + });
714 + }
715 +
716 + function simulate(seed, actions) {
717 + const rand = gen.create(seed);
718 + let finalExpectedValues = initialValues;
719 + function updateRoot() {
720 + ReactNoop.render(
721 + <Root
722 + maxDepth={maxDepth}
723 + rand={rand}
724 + values={finalExpectedValues}
725 + />,
726 + );
727 + }
728 + updateRoot();
729 +
730 + actions.forEach(action => {
731 + switch (action.type) {
732 + case FLUSH_ALL:
733 + ReactNoop.flush();
734 + break;
735 + case FLUSH:
736 + ReactNoop.flushUnitsOfWork(action.unitsOfWork);
737 + break;
738 + case UPDATE:
739 + finalExpectedValues = {
740 + ...finalExpectedValues,
741 + [action.key]: action.value,
742 + };
743 + updateRoot();
744 + break;
745 + default:
746 + throw new Error('Switch statement should be exhaustive');
747 + }
748 + assertConsistentTree();
749 + });
750 +
751 + ReactNoop.flush();
752 + assertConsistentTree(finalExpectedValues);
753 + }
754 +
755 + return {simulate};
756 + }
757 +
758 + it('hard-coded tests', () => {
759 + const {simulate} = ContextSimulator(5);
760 + simulate('randomSeed', [flush(3), update('A', 4)]);
761 + });
762 +
763 + it('generated tests', () => {
764 + const {simulate} = ContextSimulator(5);
765 +
766 + const LIMIT = 100;
767 + for (let i = 0; i < LIMIT; i++) {
768 + const seed = Math.random()
769 + .toString(36)
770 + .substr(2, 5);
771 + const actions = randomActions(5);
772 + try {
773 + simulate(seed, actions);
774 + } catch (error) {
775 + console.error(`
776 +Context fuzz tester error! Copy and paste the following line into the test suite:
777 + simulate('${seed}', ${actions.join(', ')});
778 +`);
779 + throw error;
780 + }
781 + }
782 + });
783 + });
784 +});
packages/react-reconciler/src/maxSigned31BitInt.js new
+13
@@ -0,0 +1,13 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 +// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
11 +// Math.pow(2, 30) - 1
12 +// 0b111111111111111111111111111111
13 +export default 1073741823;
packages/react/src/React.js
+6
@@ -18,12 +18,14 @@ import {
18 cloneElement,
19 isValidElement,
20 } from './ReactElement';
21 +import {createContext} from 'shared/ReactContext';
22 import {
23 createElementWithValidation,
24 createFactoryWithValidation,
25 cloneElementWithValidation,
26 } from './ReactElementValidator';
27 import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
28 +import {enableNewContextAPI} from 'shared/ReactFeatureFlags';
29
30 const React = {
31 Children: {
@@ -55,6 +57,10 @@ const React = {
57 },
58 };
59
60 +if (enableNewContextAPI) {
61 + React.unstable_createContext = createContext;
62 +}
63 +
64 if (__DEV__) {
65 Object.assign(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, {
66 // These should not be included in production.
packages/shared/ReactContext.js new
+91
@@ -0,0 +1,91 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 {
11 + REACT_PROVIDER_TYPE,
12 + REACT_CONTEXT_TYPE,
13 + REACT_ELEMENT_TYPE,
14 +} from 'shared/ReactSymbols';
15 +
16 +import type {
17 + ReactContext,
18 + ReactConsumer,
19 + ReactProvider,
20 + ReactNodeList,
21 +} from 'shared/ReactTypes';
22 +
23 +import warning from 'fbjs/lib/warning';
24 +
25 +export function createContext<T>(
26 + defaultValue: T,
27 + calculateChangedBits: ?(a: T, b: T) => number,
28 +): ReactContext<T> {
29 + let providerType;
30 +
31 + if (calculateChangedBits === undefined) {
32 + calculateChangedBits = null;
33 + } else {
34 + if (__DEV__) {
35 + warning(
36 + calculateChangedBits === null ||
37 + typeof calculateChangedBits === 'function',
38 + 'createContext: Expected the optional second argument to be a ' +
39 + 'function. Instead received: %s',
40 + calculateChangedBits,
41 + );
42 + }
43 + }
44 +
45 + const context: ReactContext<T> = {
46 + $$typeof: REACT_CONTEXT_TYPE,
47 + provide(value: T, children: ReactNodeList, key?: string): ReactProvider<T> {
48 + return {
49 + $$typeof: REACT_ELEMENT_TYPE,
50 + type: providerType,
51 + key: key === null || key === undefined ? null : '' + key,
52 + ref: null,
53 + props: {
54 + value,
55 + children,
56 + },
57 + };
58 + },
59 + consume(
60 + render: (value: T) => ReactNodeList,
61 + observedBits?: number,
62 + key?: string,
63 + ): ReactConsumer<T> {
64 + return {
65 + $$typeof: REACT_ELEMENT_TYPE,
66 + type: context,
67 + key: key === null || key === undefined ? null : '' + key,
68 + ref: null,
69 + props: {
70 + observedBits,
71 + render,
72 + },
73 + };
74 + },
75 + calculateChangedBits,
76 + defaultValue,
77 + currentValue: defaultValue,
78 + changedBits: 0,
79 + };
80 +
81 + providerType = {
82 + $$typeof: REACT_PROVIDER_TYPE,
83 + context,
84 + };
85 +
86 + if (__DEV__) {
87 + context._currentRenderer = null;
88 + }
89 +
90 + return context;
91 +}
packages/shared/ReactFeatureFlags.js
+2
@@ -20,6 +20,8 @@ export const enableMutatingReconciler = true;
20 export const enableNoopReconciler = false;
21 // Experimental persistent mode (Fabric):
22 export const enablePersistentReconciler = false;
23 +// Support for new context API
24 +export const enableNewContextAPI = false;
25
26 // Helps identify side effects in begin-phase lifecycle hooks and setState reducers:
27 export const debugRenderPhaseSideEffects = false;
packages/shared/ReactSymbols.js
+6
@@ -27,6 +27,12 @@ export const REACT_FRAGMENT_TYPE = hasSymbol
27 export const REACT_STRICT_MODE_TYPE = hasSymbol
28 ? Symbol.for('react.strict_mode')
29 : 0xeacc;
30 +export const REACT_PROVIDER_TYPE = hasSymbol
31 + ? Symbol.for('react.provider')
32 + : 0xeacd;
33 +export const REACT_CONTEXT_TYPE = hasSymbol
34 + ? Symbol.for('react.context')
35 + : 0xeace;
36
37 const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
38 const FAUX_ITERATOR_SYMBOL = '@@iterator';
packages/shared/ReactTypeOfWork.js
+17 -1
@@ -7,7 +7,21 @@
7 * @flow
8 */
9
10 -export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
10 +export type TypeOfWork =
11 + | 0
12 + | 1
13 + | 2
14 + | 3
15 + | 4
16 + | 5
17 + | 6
18 + | 7
19 + | 8
20 + | 9
21 + | 10
22 + | 11
23 + | 12
24 + | 13;
25
26 export const IndeterminateComponent = 0; // Before we know whether it is functional or class
27 export const FunctionalComponent = 1;
@@ -21,3 +35,5 @@ export const CallHandlerPhase = 8;
35 export const ReturnComponent = 9;
36 export const Fragment = 10;
37 export const Mode = 11;
38 +export const ContextConsumer = 12;
39 +export const ContextProvider = 13;
packages/shared/ReactTypes.js
+47 -1
@@ -14,7 +14,9 @@ export type ReactNode =
14 | ReactReturn<any>
15 | ReactPortal
16 | ReactText
17 - | ReactFragment;
17 + | ReactFragment
18 + | ReactProvider<any>
19 + | ReactConsumer<any>;
20
21 export type ReactFragment = ReactEmpty | Iterable<React$Node>;
22
@@ -47,6 +49,50 @@ export type ReactReturn<V> = {
49 },
50 };
51
52 +export type ReactProvider<T> = {
53 + $$typeof: Symbol | number,
54 + type: ReactProviderType<T>,
55 + key: null | string,
56 + ref: null,
57 + props: {
58 + value: T,
59 + children?: ReactNodeList,
60 + },
61 +};
62 +
63 +export type ReactProviderType<T> = {
64 + $$typeof: Symbol | number,
65 + context: ReactContext<T>,
66 +};
67 +
68 +export type ReactConsumer<T> = {
69 + $$typeof: Symbol | number,
70 + type: ReactContext<T>,
71 + key: null | string,
72 + ref: null,
73 + props: {
74 + render: (value: T) => ReactNodeList,
75 + bits?: number,
76 + },
77 +};
78 +
79 +export type ReactContext<T> = {
80 + $$typeof: Symbol | number,
81 + provide(value: T, children: ReactNodeList, key?: string): ReactProvider<T>,
82 + consume(
83 + render: (value: T) => ReactNodeList,
84 + observedBits?: number,
85 + key?: string,
86 + ): ReactConsumer<T>,
87 + calculateChangedBits: ((a: T, b: T) => number) | null,
88 + defaultValue: T,
89 + currentValue: T,
90 + changedBits: number,
91 +
92 + // DEV only
93 + _currentRenderer?: Object | null,
94 +};
95 +
96 export type ReactPortal = {
97 $$typeof: Symbol | number,
98 key: null | string,
packages/shared/forks/ReactFeatureFlags.native-fabric.js
+1
@@ -17,6 +17,7 @@ export const enableAsyncSubtreeAPI = true;
17 export const enableCreateRoot = false;
18 export const enableUserTimingAPI = __DEV__;
19 export const warnAboutDeprecatedLifecycles = false;
20 +export const enableNewContextAPI = false;
21
22 // React Fabric uses persistent reconciler.
23 export const enableMutatingReconciler = false;
packages/shared/forks/ReactFeatureFlags.native.js
+1
@@ -25,6 +25,7 @@ export const enableUserTimingAPI = __DEV__;
25 export const enableMutatingReconciler = true;
26 export const enableNoopReconciler = false;
27 export const enablePersistentReconciler = false;
28 +export const enableNewContextAPI = false;
29
30 // Only used in www builds.
31 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -19,6 +19,7 @@ export const {
19 // The rest of the flags are static for better dead code elimination.
20 export const enableAsyncSubtreeAPI = true;
21 export const enableCreateRoot = true;
22 +export const enableNewContextAPI = true;
23
24 // The www bundles only use the mutating reconciler.
25 export const enableMutatingReconciler = true;
yarn.lock
+6
@@ -4373,6 +4373,12 @@ qs@~6.5.1:
4373 version "6.5.1"
4374 resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
4375
4376 +random-seed@^0.3.0:
4377 + version "0.3.0"
4378 + resolved "https://registry.yarnpkg.com/random-seed/-/random-seed-0.3.0.tgz#d945f2e1f38f49e8d58913431b8bf6bb937556cd"
4379 + dependencies:
4380 + json-stringify-safe "^5.0.1"
4381 +
4382 randomatic@^1.1.3:
4383 version "1.1.6"
4384 resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb"