diff --git a/packages/react-art/src/ReactART.js b/packages/react-art/src/ReactART.js
index 04478434e8b..a7f9a3064ce 100644
--- a/packages/react-art/src/ReactART.js
+++ b/packages/react-art/src/ReactART.js
@@ -478,6 +478,9 @@ const ARTRenderer = ReactFiberReconciler({
now: ReactScheduler.now,
+ // The ART renderer is secondary to the React DOM renderer.
+ isPrimaryRenderer: false,
+
mutation: {
appendChild(parentInstance, child) {
if (child.parentNode === parentInstance) {
diff --git a/packages/react-art/src/__tests__/ReactART-test.js b/packages/react-art/src/__tests__/ReactART-test.js
index fbce04a9a2d..ace737e5260 100644
--- a/packages/react-art/src/__tests__/ReactART-test.js
+++ b/packages/react-art/src/__tests__/ReactART-test.js
@@ -339,6 +339,60 @@ describe('ReactART', () => {
doClick(instance);
expect(onClick2).toBeCalled();
});
+
+ it('can concurrently render with a "primary" renderer while sharing context', () => {
+ const CurrentRendererContext = React.createContext(null);
+
+ function Yield(props) {
+ testRenderer.unstable_yield(props.value);
+ return null;
+ }
+
+ let ops = [];
+ function LogCurrentRenderer() {
+ return (
+
+ {currentRenderer => {
+ ops.push(currentRenderer);
+ return null;
+ }}
+
+ );
+ }
+
+ // Using test renderer instead of the DOM renderer here because async
+ // testing APIs for the DOM renderer don't exist.
+ const testRenderer = renderer.create(
+
+
+
+
+
+ ,
+ {
+ unstable_isAsync: true,
+ },
+ );
+
+ testRenderer.unstable_flushThrough(['A']);
+
+ ReactDOM.render(
+
+
+
+
+
+ ,
+ container,
+ );
+
+ expect(ops).toEqual([null, 'ART']);
+
+ ops = [];
+ expect(testRenderer.unstable_flushAll()).toEqual(['B', 'C']);
+
+ expect(ops).toEqual(['Test']);
+ });
});
describe('ReactARTComponents', () => {
diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js
index 82032f1d1d2..81683c47748 100644
--- a/packages/react-dom/src/client/ReactDOM.js
+++ b/packages/react-dom/src/client/ReactDOM.js
@@ -690,6 +690,8 @@ const DOMRenderer = ReactFiberReconciler({
now: ReactScheduler.now,
+ isPrimaryRenderer: true,
+
mutation: {
commitMount(
domElement: Instance,
diff --git a/packages/react-native-renderer/src/ReactFabricRenderer.js b/packages/react-native-renderer/src/ReactFabricRenderer.js
index 0ec040bddf0..fa2fabef644 100644
--- a/packages/react-native-renderer/src/ReactFabricRenderer.js
+++ b/packages/react-native-renderer/src/ReactFabricRenderer.js
@@ -217,6 +217,9 @@ const ReactFabricRenderer = ReactFiberReconciler({
now: ReactNativeFrameScheduling.now,
+ // The Fabric renderer is secondary to the existing React Native renderer.
+ isPrimaryRenderer: false,
+
prepareForCommit(): void {
// Noop
},
diff --git a/packages/react-native-renderer/src/ReactNativeFiberRenderer.js b/packages/react-native-renderer/src/ReactNativeFiberRenderer.js
index 7de768b3b29..2854f6488fd 100644
--- a/packages/react-native-renderer/src/ReactNativeFiberRenderer.js
+++ b/packages/react-native-renderer/src/ReactNativeFiberRenderer.js
@@ -169,6 +169,8 @@ const NativeRenderer = ReactFiberReconciler({
now: ReactNativeFrameScheduling.now,
+ isPrimaryRenderer: true,
+
prepareForCommit(): void {
// Noop
},
diff --git a/packages/react-noop-renderer/src/ReactNoop.js b/packages/react-noop-renderer/src/ReactNoop.js
index 5cd6df0fbd2..e9c82cbe9c6 100644
--- a/packages/react-noop-renderer/src/ReactNoop.js
+++ b/packages/react-noop-renderer/src/ReactNoop.js
@@ -185,6 +185,8 @@ let SharedHostConfig = {
now(): number {
return elapsedTimeInMs;
},
+
+ isPrimaryRenderer: true,
};
const NoopRenderer = ReactFiberReconciler({
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index 489393bebbc..14927b2db98 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -104,7 +104,11 @@ export default function(
const {pushHostContext, pushHostContainer} = hostContext;
- const {pushProvider} = newContext;
+ const {
+ pushProvider,
+ getContextCurrentValue,
+ getContextChangedBits,
+ } = newContext;
const {
markActualRenderTimeStarted,
@@ -1048,8 +1052,8 @@ export default function(
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
- const newValue = context._currentValue;
- const changedBits = context._changedBits;
+ const newValue = getContextCurrentValue(context);
+ const changedBits = getContextChangedBits(context);
if (hasLegacyContextChanged()) {
// Normally we can bail out on props equality but if context has changed
diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js
index ab9c27e88aa..3aaf553ec55 100644
--- a/packages/react-reconciler/src/ReactFiberNewContext.js
+++ b/packages/react-reconciler/src/ReactFiberNewContext.js
@@ -11,14 +11,16 @@ import type {Fiber} from './ReactFiber';
import type {ReactContext} from 'shared/ReactTypes';
import type {StackCursor, Stack} from './ReactFiberStack';
-import warning from 'fbjs/lib/warning';
-
export type NewContext = {
pushProvider(providerFiber: Fiber): void,
popProvider(providerFiber: Fiber): void,
+ getContextCurrentValue(context: ReactContext): any,
+ getContextChangedBits(context: ReactContext): number,
};
-export default function(stack: Stack) {
+import warning from 'fbjs/lib/warning';
+
+export default function(stack: Stack, isPrimaryRenderer: boolean) {
const {createCursor, push, pop} = stack;
const providerCursor: StackCursor = createCursor(null);
@@ -34,21 +36,38 @@ export default function(stack: Stack) {
function pushProvider(providerFiber: Fiber): void {
const context: ReactContext = providerFiber.type._context;
- push(changedBitsCursor, context._changedBits, providerFiber);
- push(valueCursor, context._currentValue, providerFiber);
- push(providerCursor, providerFiber, providerFiber);
-
- context._currentValue = providerFiber.pendingProps.value;
- context._changedBits = providerFiber.stateNode;
-
- if (__DEV__) {
- warning(
- context._currentRenderer === null ||
- context._currentRenderer === rendererSigil,
- 'Detected multiple renderers concurrently rendering the ' +
- 'same context provider. This is currently unsupported.',
- );
- context._currentRenderer = rendererSigil;
+ if (isPrimaryRenderer) {
+ push(changedBitsCursor, context._changedBits, providerFiber);
+ push(valueCursor, context._currentValue, providerFiber);
+ push(providerCursor, providerFiber, providerFiber);
+
+ context._currentValue = providerFiber.pendingProps.value;
+ context._changedBits = providerFiber.stateNode;
+ if (__DEV__) {
+ warning(
+ context._currentRenderer === null ||
+ context._currentRenderer === rendererSigil,
+ 'Detected multiple renderers concurrently rendering the ' +
+ 'same context provider. This is currently unsupported.',
+ );
+ context._currentRenderer = rendererSigil;
+ }
+ } else {
+ push(changedBitsCursor, context._changedBits2, providerFiber);
+ push(valueCursor, context._currentValue2, providerFiber);
+ push(providerCursor, providerFiber, providerFiber);
+
+ context._currentValue2 = providerFiber.pendingProps.value;
+ context._changedBits2 = providerFiber.stateNode;
+ if (__DEV__) {
+ warning(
+ context._currentRenderer2 === null ||
+ context._currentRenderer2 === rendererSigil,
+ 'Detected multiple renderers concurrently rendering the ' +
+ 'same context provider. This is currently unsupported.',
+ );
+ context._currentRenderer2 = rendererSigil;
+ }
}
}
@@ -61,12 +80,27 @@ export default function(stack: Stack) {
pop(changedBitsCursor, providerFiber);
const context: ReactContext = providerFiber.type._context;
- context._currentValue = currentValue;
- context._changedBits = changedBits;
+ if (isPrimaryRenderer) {
+ context._currentValue = currentValue;
+ context._changedBits = changedBits;
+ } else {
+ context._currentValue2 = currentValue;
+ context._changedBits2 = changedBits;
+ }
+ }
+
+ function getContextCurrentValue(context: ReactContext): any {
+ return isPrimaryRenderer ? context._currentValue : context._currentValue2;
+ }
+
+ function getContextChangedBits(context: ReactContext): number {
+ return isPrimaryRenderer ? context._changedBits : context._changedBits2;
}
return {
pushProvider,
popProvider,
+ getContextCurrentValue,
+ getContextChangedBits,
};
}
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js
index dd395af7530..52a42646ba7 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.js
@@ -95,6 +95,11 @@ export type HostConfig = {
now(): number,
+ // Temporary workaround for scenario where multiple renderers concurrently
+ // render using the same context objects. E.g. React DOM and React ART on the
+ // same page. DOM is the primary renderer; ART is the secondary renderer.
+ isPrimaryRenderer: boolean,
+
+hydration?: HydrationHostConfig,
+mutation?: MutableUpdatesHostConfig,
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js
index 5c9657876c5..562039393da 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.js
@@ -181,7 +181,7 @@ export default function(
const stack = ReactFiberStack();
const hostContext = ReactFiberHostContext(config, stack);
const legacyContext = ReactFiberLegacyContext(stack);
- const newContext = ReactFiberNewContext(stack);
+ const newContext = ReactFiberNewContext(stack, config.isPrimaryRenderer);
const profilerTimer = createProfilerTimer(now);
const {popHostContext, popHostContainer} = hostContext;
const {
diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js
index 561aed6c584..7fa54534d31 100644
--- a/packages/react-test-renderer/src/ReactTestRenderer.js
+++ b/packages/react-test-renderer/src/ReactTestRenderer.js
@@ -226,6 +226,8 @@ const TestRenderer = ReactFiberReconciler({
// Even after the reconciler has initialized and read host config values.
now: () => nowImplementation(),
+ isPrimaryRenderer: true,
+
mutation: {
commitUpdate(
instance: Instance,
diff --git a/packages/react/src/ReactContext.js b/packages/react/src/ReactContext.js
index 10190accc4e..d15163e8134 100644
--- a/packages/react/src/ReactContext.js
+++ b/packages/react/src/ReactContext.js
@@ -36,7 +36,14 @@ export function createContext(
_calculateChangedBits: calculateChangedBits,
_defaultValue: defaultValue,
_currentValue: defaultValue,
+ // As a workaround to support multiple concurrent renderers, we categorize
+ // some renderers as primary and others as secondary. We only expect
+ // there to be two concurrent renderers at most: React Native (primary) and
+ // Fabric (secondary); React DOM (primary) and React ART (secondary).
+ // Secondary renderers store their context values on separate fields.
+ _currentValue2: defaultValue,
_changedBits: 0,
+ _changedBits2: 0,
// These are circular
Provider: (null: any),
Consumer: (null: any),
@@ -50,6 +57,7 @@ export function createContext(
if (__DEV__) {
context._currentRenderer = null;
+ context._currentRenderer2 = null;
}
return context;
diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js
index 689ed18bf95..c47a380a921 100644
--- a/packages/shared/ReactTypes.js
+++ b/packages/shared/ReactTypes.js
@@ -85,10 +85,13 @@ export type ReactContext = {
_defaultValue: T,
_currentValue: T,
+ _currentValue2: T,
_changedBits: number,
+ _changedBits2: number,
// DEV only
_currentRenderer?: Object | null,
+ _currentRenderer2?: Object | null,
};
export type ReactPortal = {