diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js index 27b30f9bb03e2..371a688ee5163 100644 --- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js @@ -13,6 +13,7 @@ let React; let ReactDOMServer; let PropTypes; +let ReactCurrentOwner; function normalizeCodeLocInfo(str) { return str && str.replace(/\(at .+?:\d+\)/g, '(at **)'); @@ -24,6 +25,9 @@ describe('ReactDOMServer', () => { React = require('react'); PropTypes = require('prop-types'); ReactDOMServer = require('react-dom/server'); + ReactCurrentOwner = + React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED + .ReactCurrentOwner; }); describe('renderToString', () => { @@ -431,6 +435,44 @@ describe('ReactDOMServer', () => { expect(results).toEqual([2, 1, 3, 1]); }); + it('renders with dispatcher.readContext mechanism', () => { + const Context = React.createContext(0); + + function readContext(context) { + return ReactCurrentOwner.currentDispatcher.readContext(context); + } + + function Consumer(props) { + return 'Result: ' + readContext(Context); + } + + const Indirection = React.Fragment; + + function App(props) { + return ( + + + + + + + + + + + + + + + ); + } + + const markup = ReactDOMServer.renderToString(); + // Extract the numbers rendered by the consumers + const results = markup.match(/\d+/g).map(Number); + expect(results).toEqual([2, 1, 3, 1]); + }); + it('renders context API, reentrancy', () => { const Context = React.createContext(0); diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index dc79c3fb28031..68ab086cf6e87 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -74,6 +74,7 @@ const toArray = ((React.Children.toArray: any): toArrayType); // Each stack is an array of frames which may contain nested stacks of elements. let currentDebugStacks = []; +let ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; let ReactDebugCurrentFrame; let prevGetCurrentStackImpl = null; let getCurrentServerStackImpl = () => ''; @@ -84,6 +85,15 @@ let pushCurrentDebugStack = (stack: Array) => {}; let pushElementToDebugStack = (element: ReactElement) => {}; let popCurrentDebugStack = () => {}; +let Dispatcher = { + readContext( + context: ReactContext, + observedBits: void | number | boolean, + ): T { + return context._currentValue; + }, +}; + if (__DEV__) { ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; @@ -786,49 +796,54 @@ class ReactDOMServerRenderer { return null; } - let out = ''; - while (out.length < bytes) { - if (this.stack.length === 0) { - this.exhausted = true; - break; - } - const frame: Frame = this.stack[this.stack.length - 1]; - if (frame.childIndex >= frame.children.length) { - const footer = frame.footer; - out += footer; - if (footer !== '') { - this.previousWasTextNode = false; + ReactCurrentOwner.currentDispatcher = Dispatcher; + try { + let out = ''; + while (out.length < bytes) { + if (this.stack.length === 0) { + this.exhausted = true; + break; } - this.stack.pop(); - if (frame.type === 'select') { - this.currentSelectValue = null; - } else if ( - frame.type != null && - frame.type.type != null && - frame.type.type.$$typeof === REACT_PROVIDER_TYPE - ) { - const provider: ReactProvider = (frame.type: any); - this.popProvider(provider); + const frame: Frame = this.stack[this.stack.length - 1]; + if (frame.childIndex >= frame.children.length) { + const footer = frame.footer; + out += footer; + if (footer !== '') { + this.previousWasTextNode = false; + } + this.stack.pop(); + if (frame.type === 'select') { + this.currentSelectValue = null; + } else if ( + frame.type != null && + frame.type.type != null && + frame.type.type.$$typeof === REACT_PROVIDER_TYPE + ) { + const provider: ReactProvider = (frame.type: any); + this.popProvider(provider); + } + continue; } - continue; - } - const child = frame.children[frame.childIndex++]; - if (__DEV__) { - pushCurrentDebugStack(this.stack); - // We're starting work on this frame, so reset its inner stack. - ((frame: any): FrameDev).debugElementStack.length = 0; - try { - // Be careful! Make sure this matches the PROD path below. + const child = frame.children[frame.childIndex++]; + if (__DEV__) { + pushCurrentDebugStack(this.stack); + // We're starting work on this frame, so reset its inner stack. + ((frame: any): FrameDev).debugElementStack.length = 0; + try { + // Be careful! Make sure this matches the PROD path below. + out += this.render(child, frame.context, frame.domNamespace); + } finally { + popCurrentDebugStack(); + } + } else { + // Be careful! Make sure this matches the DEV path above. out += this.render(child, frame.context, frame.domNamespace); - } finally { - popCurrentDebugStack(); } - } else { - // Be careful! Make sure this matches the DEV path above. - out += this.render(child, frame.context, frame.domNamespace); } + return out; + } finally { + ReactCurrentOwner.currentDispatcher = null; } - return out; } render(