Skip to content

Commit b6a95c3

Browse files
committed
Handle reentrancy in server renderer
Context stack should be per server renderer instance.
1 parent 4aaae58 commit b6a95c3

File tree

2 files changed

+89
-29
lines changed

2 files changed

+89
-29
lines changed

packages/react-dom/src/__tests__/ReactServerRendering-test.internal.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,59 @@ describe('ReactDOMServer', () => {
667667
expect(results).toEqual([2, 1, 3, 1]);
668668
});
669669

670+
it('renders context API, reentrancy', () => {
671+
const Context = React.unstable_createContext(0);
672+
673+
function Provider(props) {
674+
return Context.provide(props.value, props.children);
675+
}
676+
677+
function Consumer(props) {
678+
return Context.consume(value => {
679+
return 'Result: ' + value;
680+
});
681+
}
682+
683+
let reentrantMarkup;
684+
function Reentrant() {
685+
reentrantMarkup = ReactDOMServer.renderToString(
686+
<App value={1} reentrant={false} />,
687+
);
688+
return null;
689+
}
690+
691+
const Indirection = React.Fragment;
692+
693+
function App(props) {
694+
return (
695+
<Provider value={props.value}>
696+
{props.reentrant && <Reentrant />}
697+
<Provider value={2}>
698+
<Consumer />
699+
</Provider>
700+
<Indirection>
701+
<Indirection>
702+
<Consumer />
703+
<Provider value={3}>
704+
<Consumer />
705+
</Provider>
706+
</Indirection>
707+
</Indirection>
708+
<Consumer />
709+
</Provider>
710+
);
711+
}
712+
713+
const markup = ReactDOMServer.renderToString(
714+
<App value={1} reentrant={true} />,
715+
);
716+
// Extract the numbers rendered by the consumers
717+
const results = markup.match(/\d+/g).map(Number);
718+
const reentrantResults = reentrantMarkup.match(/\d+/g).map(Number);
719+
expect(results).toEqual([2, 1, 3, 1]);
720+
expect(reentrantResults).toEqual([2, 1, 3, 1]);
721+
});
722+
670723
it('renders components with different batching strategies', () => {
671724
class StaticComponent extends React.Component {
672725
render() {

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

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -123,33 +123,6 @@ if (__DEV__) {
123123
};
124124
}
125125

126-
// Context (new API)
127-
let providerStack: Array<ReactProvider<any>> = []; // Stack of provider objects
128-
let index = -1;
129-
130-
export function pushProvider<T>(provider: ReactProvider<T>): void {
131-
index += 1;
132-
providerStack[index] = provider;
133-
const context: ReactContext<any> = provider.type.context;
134-
context.currentProvider = provider;
135-
}
136-
137-
export function popProvider<T>(provider: ReactProvider<T>): void {
138-
if (__DEV__) {
139-
warning(index > -1 && provider === providerStack[index], 'Unexpected pop.');
140-
}
141-
// $FlowFixMe - Intentionally unsound
142-
providerStack[index] = null;
143-
index -= 1;
144-
const context: ReactContext<any> = provider.type.context;
145-
if (index < 0) {
146-
context.currentProvider = null;
147-
} else {
148-
const previousProvider = providerStack[index];
149-
context.currentProvider = previousProvider;
150-
}
151-
}
152-
153126
let didWarnDefaultInputValue = false;
154127
let didWarnDefaultChecked = false;
155128
let didWarnDefaultSelectValue = false;
@@ -565,6 +538,9 @@ class ReactDOMServerRenderer {
565538
previousWasTextNode: boolean;
566539
makeStaticMarkup: boolean;
567540

541+
providerStack: Array<ReactProvider<any>>;
542+
providerIndex: number;
543+
568544
constructor(children: mixed, makeStaticMarkup: boolean) {
569545
const flatChildren = flattenTopLevelChildren(children);
570546

@@ -586,6 +562,37 @@ class ReactDOMServerRenderer {
586562
this.currentSelectValue = null;
587563
this.previousWasTextNode = false;
588564
this.makeStaticMarkup = makeStaticMarkup;
565+
566+
// Context (new API)
567+
this.providerStack = []; // Stack of provider objects
568+
this.providerIndex = -1;
569+
}
570+
571+
pushProvider<T>(provider: ReactProvider<T>): void {
572+
this.providerIndex += 1;
573+
this.providerStack[this.providerIndex] = provider;
574+
const context: ReactContext<any> = provider.type.context;
575+
context.currentProvider = provider;
576+
}
577+
578+
popProvider<T>(provider: ReactProvider<T>): void {
579+
if (__DEV__) {
580+
warning(
581+
this.providerIndex > -1 &&
582+
provider === this.providerStack[this.providerIndex],
583+
'Unexpected pop.',
584+
);
585+
}
586+
// $FlowFixMe - Intentionally unsound
587+
this.providerStack[this.providerIndex] = null;
588+
this.providerIndex -= 1;
589+
const context: ReactContext<any> = provider.type.context;
590+
if (this.providerIndex < 0) {
591+
context.currentProvider = null;
592+
} else {
593+
const previousProvider = this.providerStack[this.providerIndex];
594+
context.currentProvider = previousProvider;
595+
}
589596
}
590597

591598
read(bytes: number): string | null {
@@ -615,7 +622,7 @@ class ReactDOMServerRenderer {
615622
frame.type.type.$$typeof === REACT_PROVIDER_TYPE
616623
) {
617624
const provider: ReactProvider<any> = (frame.type: any);
618-
popProvider(provider);
625+
this.popProvider(provider);
619626
}
620627
continue;
621628
}
@@ -743,7 +750,7 @@ class ReactDOMServerRenderer {
743750
((frame: any): FrameDev).debugElementStack = [];
744751
}
745752

746-
pushProvider(provider);
753+
this.pushProvider(provider);
747754

748755
this.stack.push(frame);
749756
return '';

0 commit comments

Comments
 (0)