Skip to content

Commit 1e5100e

Browse files
committed
[RFC] FIX: Context pollutes mutable global state (with "thread local storage")
Fixes facebook#13874 Alternative to facebook#13875 This straw-man approach also avoids mutating global context by giving each ReactPartialRenderer it's own mutable state and using context instances as keys into that mutable state.
1 parent 8bf2ed6 commit 1e5100e

File tree

1 file changed

+18
-5
lines changed

1 file changed

+18
-5
lines changed

packages/react-dom/src/server/ReactPartialRenderer.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,7 @@ class ReactDOMServerRenderer {
695695
contextIndex: number;
696696
contextStack: Array<ReactContext<any>>;
697697
contextValueStack: Array<any>;
698+
contextValueMap: Map<ReactContext<any>, any>;
698699
contextProviderStack: ?Array<ReactProvider<any>>; // DEV-only
699700

700701
constructor(children: mixed, makeStaticMarkup: boolean) {
@@ -723,6 +724,7 @@ class ReactDOMServerRenderer {
723724
this.contextIndex = -1;
724725
this.contextStack = [];
725726
this.contextValueStack = [];
727+
this.contextValueMap = new Map();
726728
if (__DEV__) {
727729
this.contextProviderStack = [];
728730
}
@@ -736,12 +738,21 @@ class ReactDOMServerRenderer {
736738
* we mutated it, onto the stacks. Therefore, on the way up, we always know which
737739
* provider needs to be "restored" to which value.
738740
* https://github.com/facebook/react/pull/12985#issuecomment-396301248
741+
*
742+
* A context's _currentValue is not directly mutated to avoid issues with
743+
* concurrent renderers. Instead a local contextValueMap stores the current
744+
* value for each context instance.
739745
*/
740746

741747
pushProvider<T>(provider: ReactProvider<T>): void {
742748
const index = ++this.contextIndex;
743-
const context: ReactContext<any> = provider.type._context;
744-
const previousValue = context._currentValue;
749+
// NOTE: in __DEV__ the _context and Consumer references are different,
750+
// while in production they are the same. For consistent Map keys, always
751+
// use context.Consumer.
752+
const context: ReactContext<any> = provider.type._context.Consumer;
753+
const previousValue = this.contextValueMap.has(context)
754+
? this.contextValueMap.get(context)
755+
: context._currentValue;
745756

746757
// Remember which value to restore this context to on our way up.
747758
this.contextStack[index] = context;
@@ -752,7 +763,7 @@ class ReactDOMServerRenderer {
752763
}
753764

754765
// Mutate the current value.
755-
context._currentValue = provider.props.value;
766+
this.contextValueMap.set(context, provider.props.value);
756767
}
757768

758769
popProvider<T>(provider: ReactProvider<T>): void {
@@ -778,7 +789,7 @@ class ReactDOMServerRenderer {
778789
this.contextIndex--;
779790

780791
// Restore to the previous value we stored as we were walking down.
781-
context._currentValue = previousValue;
792+
this.contextValueMap.set(context, previousValue);
782793
}
783794

784795
read(bytes: number): string | null {
@@ -988,7 +999,9 @@ class ReactDOMServerRenderer {
988999
case REACT_CONTEXT_TYPE: {
9891000
const consumer: ReactConsumer<any> = (nextChild: any);
9901001
const nextProps: any = consumer.props;
991-
const nextValue = consumer.type._currentValue;
1002+
const nextValue = this.contextValueMap.has(consumer.type)
1003+
? this.contextValueMap.get(consumer.type)
1004+
: consumer.type._currentValue;
9921005

9931006
const nextChildren = toArray(nextProps.children(nextValue));
9941007
const frame: Frame = {

0 commit comments

Comments
 (0)