diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js index a07a842b334f..6524b06671c8 100644 --- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js @@ -664,4 +664,60 @@ describe('ReactDOMServer', () => { require('react-dom'); }).not.toThrow(); }); + + it('includes a useful stack in warnings', () => { + function A() { + return null; + } + + function B() { + return ( + + + + + + ); + } + + class C extends React.Component { + render() { + return {this.props.children}; + } + } + + function Child() { + return [, , ]; + } + + function App() { + return ( +
+
+ + + +
+ ); + } + + expect(() => ReactDOMServer.renderToString()).toWarnDev([ + 'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + + ' in span (at **)\n' + + ' in b (at **)\n' + + ' in C (at **)\n' + + ' in font (at **)\n' + + ' in B (at **)\n' + + ' in Child (at **)\n' + + ' in span (at **)\n' + + ' in div (at **)\n' + + ' in App (at **)', + 'Invalid ARIA attribute `ariaTypo2`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + + ' in span (at **)\n' + + ' in Child (at **)\n' + + ' in span (at **)\n' + + ' in div (at **)\n' + + ' in App (at **)', + ]); + }); }); diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index f3c20cde346e..e502687a3ec3 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -62,14 +62,12 @@ type toArrayType = (children: mixed) => FlatReactChildren; const toArray = ((React.Children.toArray: any): toArrayType); let currentDebugStack; -let currentDebugElementStack; let getStackAddendum = () => ''; let describeStackFrame = element => ''; let validatePropertiesInDevelopment = (type, props) => {}; let setCurrentDebugStack = (stack: Array) => {}; -let pushElementToDebugStack = (element: ReactElement) => {}; let resetCurrentDebugStack = () => {}; if (__DEV__) { @@ -88,22 +86,11 @@ if (__DEV__) { }; currentDebugStack = null; - currentDebugElementStack = null; setCurrentDebugStack = function(stack: Array) { - const frame: Frame = stack[stack.length - 1]; - currentDebugElementStack = ((frame: any): FrameDev).debugElementStack; - // We are about to enter a new composite stack, reset the array. - currentDebugElementStack.length = 0; currentDebugStack = stack; ReactDebugCurrentFrame.getCurrentStack = getStackAddendum; }; - pushElementToDebugStack = function(element: ReactElement) { - if (currentDebugElementStack !== null) { - currentDebugElementStack.push(element); - } - }; resetCurrentDebugStack = function() { - currentDebugElementStack = null; currentDebugStack = null; ReactDebugCurrentFrame.getCurrentStack = null; }; @@ -388,6 +375,7 @@ function validateRenderResult(child, type) { function resolve( child: mixed, context: Object, + debugElementStack: Array | null, ): {| child: mixed, context: Object, @@ -397,7 +385,7 @@ function resolve( let element: ReactElement = (child: any); let Component = element.type; if (__DEV__) { - pushElementToDebugStack(element); + ((debugElementStack: any): Array).push(element); } if (typeof Component !== 'function') { break; @@ -773,10 +761,19 @@ class ReactDOMServerRenderer { continue; } const child = frame.children[frame.childIndex++]; + let debugElementStack = null; if (__DEV__) { setCurrentDebugStack(this.stack); + debugElementStack = ((frame: any): FrameDev).debugElementStack; + // We are about to enter a new composite stack, reset the array. + debugElementStack.length = 0; } - out += this.render(child, frame.context, frame.domNamespace); + out += this.render( + child, + frame.context, + frame.domNamespace, + debugElementStack, + ); if (__DEV__) { // TODO: Handle reentrant server render calls. This doesn't. resetCurrentDebugStack(); @@ -789,6 +786,7 @@ class ReactDOMServerRenderer { child: ReactNode | null, context: Object, parentNamespace: string, + debugElementStack: Array | null, ): string { if (typeof child === 'string' || typeof child === 'number') { const text = '' + child; @@ -805,7 +803,11 @@ class ReactDOMServerRenderer { return escapeTextForBrowser(text); } else { let nextChild; - ({child: nextChild, context} = resolve(child, context)); + ({child: nextChild, context} = resolve( + child, + context, + debugElementStack, + )); if (nextChild === null || nextChild === false) { return ''; } else if (!React.isValidElement(nextChild)) {