diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js
index af9a6095e0a46..334ff0e2cbd2c 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js
@@ -249,5 +249,38 @@ describe('ReactDOMServerIntegration', () => {
expect(e.querySelector('#language2').textContent).toBe('sanskrit');
expect(e.querySelector('#language3').textContent).toBe('french');
});
+
+ it('does not pollute parallel node streams', () => {
+ const LoggedInUser = React.createContext();
+
+ const AppWithUser = user => (
+
+
+
+
+ );
+
+ const streamAmy = ReactDOMServer.renderToNodeStream(
+ AppWithUser('Amy'),
+ ).setEncoding('utf8');
+ const streamBob = ReactDOMServer.renderToNodeStream(
+ AppWithUser('Bob'),
+ ).setEncoding('utf8');
+
+ // Testing by filling the buffer using internal _read() with a small
+ // number of bytes to avoid a test case which needs to align to a
+ // highWaterMark boundary of 2^14 chars.
+ streamAmy._read(20);
+ streamBob._read(20);
+ streamAmy._read(20);
+ streamBob._read(20);
+
+ expect(streamAmy.read()).toBe('');
+ expect(streamBob.read()).toBe('');
+ });
});
});
diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js
index dc79c3fb28031..68342ceecad24 100644
--- a/packages/react-dom/src/server/ReactPartialRenderer.js
+++ b/packages/react-dom/src/server/ReactPartialRenderer.js
@@ -695,6 +695,7 @@ class ReactDOMServerRenderer {
contextIndex: number;
contextStack: Array>;
contextValueStack: Array;
+ contextValueMap: Map, any>;
contextProviderStack: ?Array>; // DEV-only
constructor(children: mixed, makeStaticMarkup: boolean) {
@@ -723,6 +724,7 @@ class ReactDOMServerRenderer {
this.contextIndex = -1;
this.contextStack = [];
this.contextValueStack = [];
+ this.contextValueMap = new Map();
if (__DEV__) {
this.contextProviderStack = [];
}
@@ -736,12 +738,21 @@ class ReactDOMServerRenderer {
* we mutated it, onto the stacks. Therefore, on the way up, we always know which
* provider needs to be "restored" to which value.
* https://github.com/facebook/react/pull/12985#issuecomment-396301248
+ *
+ * A context's _currentValue is not directly mutated to avoid issues with
+ * concurrent renderers. Instead a local contextValueMap stores the current
+ * value for each context instance.
*/
pushProvider(provider: ReactProvider): void {
const index = ++this.contextIndex;
- const context: ReactContext = provider.type._context;
- const previousValue = context._currentValue;
+ // NOTE: in __DEV__ the _context and Consumer references are different,
+ // while in production they are the same. For consistent Map keys, always
+ // use context.Consumer.
+ const context: ReactContext = provider.type._context.Consumer;
+ const previousValue = this.contextValueMap.has(context)
+ ? this.contextValueMap.get(context)
+ : context._currentValue;
// Remember which value to restore this context to on our way up.
this.contextStack[index] = context;
@@ -752,7 +763,7 @@ class ReactDOMServerRenderer {
}
// Mutate the current value.
- context._currentValue = provider.props.value;
+ this.contextValueMap.set(context, provider.props.value);
}
popProvider(provider: ReactProvider): void {
@@ -778,7 +789,7 @@ class ReactDOMServerRenderer {
this.contextIndex--;
// Restore to the previous value we stored as we were walking down.
- context._currentValue = previousValue;
+ this.contextValueMap.set(context, previousValue);
}
read(bytes: number): string | null {
@@ -988,7 +999,9 @@ class ReactDOMServerRenderer {
case REACT_CONTEXT_TYPE: {
const consumer: ReactConsumer = (nextChild: any);
const nextProps: any = consumer.props;
- const nextValue = consumer.type._currentValue;
+ const nextValue = this.contextValueMap.has(consumer.type)
+ ? this.contextValueMap.get(consumer.type)
+ : consumer.type._currentValue;
const nextChildren = toArray(nextProps.children(nextValue));
const frame: Frame = {