@samitouri / QOS-React / commits / e36b38c1ca

[Fiber] Fix some of the warnings (#8570)

* Implement component stack for some warnings in Fiber * Keep Fiber debug source up to date When an element changes, we should copy the source and owner again. Otherwise they can get stale since we're not reading them from the element. * Remove outdated TODOs from tests * Explicitly specify Fiber types to include in the stack Fixes an accidental omission when both source and owner are null but displayName exists. * Fix mised Stack+Fiber test to not expect extra warnings When we're in Fiber mode we don't actually expect that warning being printed. * Warn on passing different props to super() * Implement duplicate key warnings We keep known keys in a set in development. There is an annoying special case where we know we'll check it again because we break out of the loop early. One test in the tree hook regresses to the failing state because it checks that the tree hook works without a Set available, but we started using Set in this code. It is not essential and we can clean this up later when we decide how to deal with polyfills. * Move ReactTypeOfWork to src/shared It needs to be available both to Fiber and Isomorphic because the tree hook lives in Isomorphic but pretty-prints Fiber stack. * Add dev-only ReactDebugCurrentFiber for warnings The goal is to use ReactCurrentOwner less and rely on ReactDebugCurrentFiber for warning owner name and stack. * Make Stack invariant messages more consistent Fiber used a helper so two tests had the same phrasing. Stack also used a helper for most invariants but hardcoded a different phrase in one place. I changed that invariant message to use a helper which made it consistent with what it prints in Fiber. * Make CSSPropertyOperations use getCurrentFiberOwnerName() This gets mount-time CSS warnings to be printed. However update-time warnings are currently ignored because current fiber is not yet available during the commit phase. We also regress on HostOperation hook tests but this doesn't matter because it's only used by ReactPerf and it doesn't work with Fiber yet anyway. We'll have to think more about it later. * Set ReactDebugCurrentFiber during the commit phase This makes it available during updates, fixing the last failing test in CSSPropertyOperations. * Add DOM warnings by calling hooks directly It is not clear if the old hook system is worth it in its generic incarnation. For now I am just hooking it up to the DOMFiber renderer directly. * Add client-side counterparts for some warning tests This helps us track which warnings are really failing in Fiber, and which ones depend on SSR.

Dan Abramov committed Dec 15, 2016 at 08:36 UTC e36b38c1cad22f7dff557736cf4b0280106e937e
31 files changed +870 -249
scripts/fiber/tests-failing.txt
+3 -10
@@ -12,9 +12,6 @@ src/isomorphic/classic/__tests__/ReactContextValidator-test.js
12 src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
13 * includes the owner name when passing null, undefined, boolean, or number
14
15 -src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
16 -* should give context for PropType errors in nested components.
17 -
15 src/renderers/dom/__tests__/ReactDOMProduction-test.js
16 * should throw with an error code in production
17
@@ -23,14 +20,11 @@ src/renderers/dom/shared/__tests__/ReactDOM-test.js
20 * throws in render() if the update callback is not a function
21
22 src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
26 -* should warn for children on void elements
27 -* should report component containing invalid styles
23 * should clean up input value tracking
24 * should clean up input textarea tracking
30 -* gives source code refs for unknown prop warning
31 -* gives source code refs for unknown prop warning for update render
32 -* gives source code refs for unknown prop warning for exact elements
33 -* gives source code refs for unknown prop warning for exact elements in composition
25 +* gives source code refs for unknown prop warning (ssr)
26 +* gives source code refs for unknown prop warning for exact elements (ssr)
27 +* gives source code refs for unknown prop warning for exact elements in composition (ssr)
28
29 src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
30 * can reconcile text merged by Node.normalize() alongside other elements
@@ -64,7 +58,6 @@ src/renderers/shared/__tests__/ReactPerf-test.js
58 * should work when measurement starts during reconciliation
59
60 src/renderers/shared/hooks/__tests__/ReactComponentTreeHook-test.js
67 -* gets created
61 * can be retrieved by ID
62
63 src/renderers/shared/hooks/__tests__/ReactHostOperationHistoryHook-test.js
scripts/fiber/tests-passing-except-dev.txt
+2 -61
@@ -1,33 +1,7 @@
1 src/addons/transitions/__tests__/ReactTransitionGroup-test.js
2 * should warn for duplicated keys with component stack info
3
4 -src/isomorphic/classic/__tests__/ReactContextValidator-test.js
5 -* should check context types
6 -* should check child context types
7 -
8 -src/isomorphic/classic/element/__tests__/ReactElementClone-test.js
9 -* should check declared prop types after clone
10 -
11 -src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
12 -* warns for keys for arrays of elements with owner info
13 -* warns for keys with component stack info
14 -* should give context for PropType errors in nested components.
15 -
16 -src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
17 -* warns for keys for arrays of elements with owner info
18 -
19 -src/renderers/dom/shared/__tests__/CSSPropertyOperations-test.js
20 -* should warn when using hyphenated style names
21 -* should warn when updating hyphenated style names
22 -* warns when miscapitalizing vendored style names
23 -* should warn about style having a trailing semicolon
24 -* should warn about style containing a NaN value
25 -
4 src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
27 -* should warn for unknown prop
28 -* should group multiple unknown prop warnings together
29 -* should warn for onDblClick prop
30 -* should emit a warning once for a named custom component using shady DOM
5 * should not warn when server-side rendering `onScroll`
6 * warns on invalid nesting
7 * warns on invalid nesting at root
@@ -35,15 +9,8 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
9 * gives useful context in warnings
10 * should warn about incorrect casing on properties (ssr)
11 * should warn about incorrect casing on event handlers (ssr)
38 -* should warn about incorrect casing on properties
39 -* should warn about incorrect casing on event handlers
40 -* should warn about class
41 -* should suggest property name if available
42 -
43 -src/renderers/dom/shared/__tests__/ReactDOMInvalidARIAHook-test.js
44 -* should warn for one invalid aria-* prop
45 -* should warn for many invalid aria-* props
46 -* should warn for an improperly cased aria-* prop
12 +* should warn about class (ssr)
13 +* should suggest property name if available (ssr)
14
15 src/renderers/dom/shared/__tests__/ReactMount-test.js
16 * should warn if mounting into dirty rendered markup
@@ -59,17 +26,6 @@ src/renderers/dom/shared/__tests__/ReactMountDestruction-test.js
26 src/renderers/dom/shared/__tests__/ReactServerRendering-test.js
27 * should have the correct mounting behavior
28
62 -src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
63 -* should warn if value is null
64 -* should warn if controlled input switches to uncontrolled (value is null)
65 -* should warn if uncontrolled input (value is null) switches to controlled
66 -
67 -src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js
68 -* should warn if value is null
69 -
70 -src/renderers/dom/shared/wrappers/__tests__/ReactDOMTextarea-test.js
71 -* should warn if value is null
72 -
29 src/renderers/shared/hooks/__tests__/ReactComponentTreeHook-test.js
30 * uses displayName or Unknown for classic components
31 * uses displayName, name, or ReactComponent for modern components
@@ -136,12 +92,8 @@ src/renderers/shared/hooks/__tests__/ReactHostOperationHistoryHook-test.js
92 * gets recorded during an update
93 * gets ignored if the styles are shallowly equal
94 * gets recorded during mount
139 -* gets recorded during an update
140 -* gets recorded as a removal during an update
95 * gets recorded during mount
142 -* gets recorded during an update
96 * gets recorded during mount
144 -* gets recorded during an update
97 * gets recorded during an update from text content
98 * gets recorded during an update from html
99 * gets recorded during an update from children
@@ -154,10 +106,6 @@ src/renderers/shared/hooks/__tests__/ReactHostOperationHistoryHook-test.js
106 * gets reported when a child is inserted
107 * gets reported when a child is removed
108
157 -src/renderers/shared/shared/__tests__/ReactChildReconciler-test.js
158 -* warns for duplicated keys
159 -* warns for duplicated keys with component stack info
160 -
109 src/renderers/shared/shared/__tests__/ReactComponentLifeCycle-test.js
110 * should correctly determine if a component is mounted
111 * should correctly determine if a null component is mounted
@@ -167,17 +115,10 @@ src/renderers/shared/shared/__tests__/ReactCompositeComponent-test.js
115 * should warn about `forceUpdate` on unmounted components
116 * should warn about `setState` on unmounted components
117 * should disallow nested render calls
170 -* should warn when mutated props are passed
118
119 src/renderers/shared/shared/__tests__/ReactMultiChild-test.js
173 -* should warn for duplicated keys with component stack info
120 * should warn for using maps as children with owner info
121
122 src/renderers/shared/shared/__tests__/ReactStatelessComponent-test.js
123 * should warn for childContextTypes on a functional component
124 * should warn when given a ref
179 -* should use correct name in key warning
180 -
181 -src/renderers/shared/shared/__tests__/refs-test.js
182 -* attaches, detaches from fiber component with stack layer
183 -* attaches, detaches from stack component with fiber layer
scripts/fiber/tests-passing.txt
+55
@@ -153,6 +153,8 @@ src/isomorphic/children/__tests__/sliceChildren-test.js
153 src/isomorphic/classic/__tests__/ReactContextValidator-test.js
154 * should filter out context not in contextTypes
155 * should pass next context to lifecycles
156 +* should check context types
157 +* should check child context types
158
159 src/isomorphic/classic/class/__tests__/ReactBind-test.js
160 * Holds reference to instance
@@ -266,20 +268,24 @@ src/isomorphic/classic/element/__tests__/ReactElementClone-test.js
268 * does not warns for arrays of elements with keys
269 * does not warn when the element is directly in rest args
270 * does not warn when the array contains a non-element
271 +* should check declared prop types after clone
272 * should ignore key and ref warning getters
273 * should ignore undefined key and ref
274 * should extract null key and ref
275
276 src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
277 * warns for keys for arrays of elements in rest args
278 +* warns for keys for arrays of elements with owner info
279 * warns for keys for arrays with no owner or parent info
280 * warns for keys for arrays of elements with no owner info
281 +* warns for keys with component stack info
282 * does not warn for keys when passing children down
283 * warns for keys for iterables of elements in rest args
284 * does not warns for arrays of elements with keys
285 * does not warns for iterable elements with keys
286 * does not warn when the element is directly in rest args
287 * does not warn when the array contains a non-element
288 +* should give context for PropType errors in nested components.
289 * gives a helpful error when passing null, undefined, boolean, or number
290 * should check default prop values
291 * should not check the default for explicit null
@@ -471,12 +477,15 @@ src/isomorphic/modern/element/__tests__/ReactJSXElement-test.js
477
478 src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
479 * warns for keys for arrays of elements in children position
480 +* warns for keys for arrays of elements with owner info
481 * warns for keys for iterables of elements in rest args
482 * does not warns for arrays of elements with keys
483 * does not warns for iterable elements with keys
484 * does not warn for numeric keys in entry iterable as a child
485 * does not warn when the element is directly as children
486 * does not warn when the child array contains non-elements
487 +* should give context for PropType errors in nested components.
488 +* should update component stack after receiving next element
489 * gives a helpful error when passing null, undefined, or boolean
490 * should check default prop values
491 * should not check the default for explicit null
@@ -543,6 +552,11 @@ src/renderers/dom/shared/__tests__/CSSPropertyOperations-test.js
552 * should create vendor-prefixed markup correctly
553 * should set style attribute when styles exist
554 * should not set style attribute when no styles exist
555 +* should warn when using hyphenated style names
556 +* should warn when updating hyphenated style names
557 +* warns when miscapitalizing vendored style names
558 +* should warn about style having a trailing semicolon
559 +* should warn about style containing a NaN value
560
561 src/renderers/dom/shared/__tests__/DOMPropertyOperations-test.js
562 * should create markup for simple properties
@@ -607,6 +621,9 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
621 * should gracefully handle various style value types
622 * should not update styles when mutating a proxy style object
623 * should throw when mutating style objectsd
624 +* should warn for unknown prop
625 +* should group multiple unknown prop warnings together
626 +* should warn for onDblClick prop
627 * should not warn for "0" as a unitless style value
628 * should warn nicely about NaN in style
629 * should update styles if initially null
@@ -644,6 +661,8 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
661 * should warn on upper case HTML tags, not SVG nor custom tags
662 * should warn against children for void elements
663 * should warn against dangerouslySetInnerHTML for void elements
664 +* should include owner rather than parent in warnings
665 +* should emit a warning once for a named custom component using shady DOM
666 * should emit a warning once for an unnamed custom component using shady DOM
667 * should treat menuitem as a void element but still create the closing tag
668 * should validate against multiple children props
@@ -656,12 +675,14 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
675 * should validate against invalid styles
676 * should track input values
677 * should track textarea values
678 +* should warn for children on void elements
679 * should support custom elements which extend native elements
680 * should warn against children for void elements
681 * should warn against dangerouslySetInnerHTML for void elements
682 * should validate against multiple children props
683 * should warn about contentEditable and children
684 * should validate against invalid styles
685 +* should report component containing invalid styles
686 * should properly escape text content and attributes values
687 * unmounts children before unsetting DOM node info
688 * should warn about the `onScroll` issue when unsupported (IE8)
@@ -669,7 +690,16 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
690 * should throw when an attack vector is used server-side
691 * should throw when an invalid tag name is used
692 * should throw when an attack vector is used
693 +* should warn about incorrect casing on properties
694 +* should warn about incorrect casing on event handlers
695 +* should warn about class
696 * should warn about props that are no longer supported
697 +* should warn about props that are no longer supported (ssr)
698 +* gives source code refs for unknown prop warning
699 +* gives source code refs for unknown prop warning for update render
700 +* gives source code refs for unknown prop warning for exact elements
701 +* gives source code refs for unknown prop warning for exact elements in composition
702 +* should suggest property name if available
703
704 src/renderers/dom/shared/__tests__/ReactDOMComponentTree-test.js
705 * finds nodes for instances
@@ -680,6 +710,9 @@ src/renderers/dom/shared/__tests__/ReactDOMIDOperations-test.js
710
711 src/renderers/dom/shared/__tests__/ReactDOMInvalidARIAHook-test.js
712 * should allow valid aria-* props
713 +* should warn for one invalid aria-* prop
714 +* should warn for many invalid aria-* props
715 +* should warn for an improperly cased aria-* prop
716
717 src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
718 * creates initial namespaced markup
@@ -933,11 +966,14 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
966 * should have a this value of undefined if bind is not used
967 * should warn with checked and no onChange handler with readOnly specified
968 * should update defaultValue to empty string
969 +* should warn if value is null
970 * should warn if checked and defaultChecked props are specified
971 * should warn if value and defaultValue props are specified
972 * should warn if controlled input switches to uncontrolled (value is undefined)
973 +* should warn if controlled input switches to uncontrolled (value is null)
974 * should warn if controlled input switches to uncontrolled with defaultValue
975 * should warn if uncontrolled input (value is undefined) switches to controlled
976 +* should warn if uncontrolled input (value is null) switches to controlled
977 * should warn if controlled checkbox switches to uncontrolled (checked is undefined)
978 * should warn if controlled checkbox switches to uncontrolled (checked is null)
979 * should warn if controlled checkbox switches to uncontrolled with defaultChecked
@@ -983,6 +1019,7 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js
1019 * should support server-side rendering with defaultValue
1020 * should support server-side rendering with multiple
1021 * should not control defaultValue if readding options
1022 +* should warn if value is null
1023 * should refresh state on change
1024 * should warn if value and defaultValue props are specified
1025 * should be able to safely remove select onChange
@@ -1015,6 +1052,7 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMTextarea-test.js
1052 * should allow objects as children
1053 * should throw with multiple or invalid children
1054 * should unmount
1055 +* should warn if value is null
1056 * should warn if value and defaultValue are specified
1057
1058 src/renderers/native/__tests__/ReactNativeAttributePayload-test.js
@@ -1204,6 +1242,7 @@ src/renderers/shared/fiber/__tests__/ReactTopLevelText-test.js
1242 * should render a component returning numbers directly from render
1243
1244 src/renderers/shared/hooks/__tests__/ReactComponentTreeHook-test.js
1245 +* gets created
1246 * is created during mounting
1247 * is created when calling renderToString during render
1248
@@ -1258,11 +1297,21 @@ src/renderers/shared/hooks/__tests__/ReactComponentTreeHook-test.native.js
1297
1298 src/renderers/shared/hooks/__tests__/ReactHostOperationHistoryHook-test.js
1299 * gets ignored for composite roots that return null
1300 +* gets recorded during an update
1301 +* gets recorded as a removal during an update
1302 +* gets recorded during an update
1303 +* gets recorded during an update
1304 * gets ignored if new text is equal
1305 * gets ignored if new text is equal
1306 * gets ignored if the type has not changed
1307 * gets ignored if new html is equal
1308
1309 +src/renderers/shared/shared/__tests__/ReactChildReconciler-test.js
1310 +* warns for duplicated array keys
1311 +* warns for duplicated array keys with component stack info
1312 +* warns for duplicated iterable keys
1313 +* warns for duplicated iterable keys with component stack info
1314 +
1315 src/renderers/shared/shared/__tests__/ReactComponent-test.js
1316 * should throw when supplying a ref outside of render method
1317 * should warn when children are mutated during render
@@ -1317,6 +1366,7 @@ src/renderers/shared/shared/__tests__/ReactCompositeComponent-test.js
1366 * should replace state
1367 * should support objects with prototypes as state
1368 * should not warn about unmounting during unmounting
1369 +* should warn when mutated props are passed
1370 * should only call componentWillUnmount once
1371 * prepares new child before unmounting old
1372
@@ -1405,6 +1455,8 @@ src/renderers/shared/shared/__tests__/ReactMultiChild-test.js
1455 * should replace children with different constructors
1456 * should NOT replace children with different owners
1457 * should replace children with different keys
1458 +* should warn for duplicated array keys with component stack info
1459 +* should warn for duplicated iterable keys with component stack info
1460 * should reorder bailed-out children
1461 * prepares new children before unmounting old
1462
@@ -1457,6 +1509,7 @@ src/renderers/shared/shared/__tests__/ReactStatelessComponent-test.js
1509 * should unmount stateless component
1510 * should pass context thru stateless component
1511 * should provide a null ref
1512 +* should use correct name in key warning
1513 * should support default props and prop types
1514 * should receive context
1515 * should work with arrow functions
@@ -1511,6 +1564,8 @@ src/renderers/shared/shared/__tests__/refs-test.js
1564 * ref called correctly for stateless component when __DEV__ = false
1565 * ref called correctly for stateless component when __DEV__ = true
1566 * coerces numbers to strings
1567 +* attaches, detaches from fiber component with stack layer
1568 +* attaches, detaches from stack component with fiber layer
1569
1570 src/renderers/shared/shared/event/__tests__/EventPluginHub-test.js
1571 * should prevent non-function listeners, at dispatch
src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
-3
@@ -258,9 +258,6 @@ describe('ReactElementValidator', () => {
258 expectDev(console.error.calls.count()).toBe(0);
259 });
260
261 - // TODO: These warnings currently come from the composite component, but
262 - // they should be moved into the ReactElementValidator.
263 -
261 it('should give context for PropType errors in nested components.', () => {
262 // In this test, we're making sure that if a proptype error is found in a
263 // component, we give a small hint as to which parent instantiated that
src/isomorphic/hooks/ReactComponentTreeHook.js
+50 -3
@@ -13,6 +13,13 @@
13 'use strict';
14
15 var ReactCurrentOwner = require('ReactCurrentOwner');
16 +var ReactTypeOfWork = require('ReactTypeOfWork');
17 +var {
18 + IndeterminateComponent,
19 + FunctionalComponent,
20 + ClassComponent,
21 + HostComponent,
22 +} = ReactTypeOfWork;
23
24 var getComponentName = require('getComponentName');
25 var invariant = require('invariant');
@@ -20,6 +27,7 @@ var warning = require('warning');
27
28 import type { ReactElement, Source } from 'ReactElementType';
29 import type { DebugID } from 'ReactInstanceType';
30 +import type { Fiber } from 'ReactFiber';
31
32 function isNative(fn) {
33 // Based on isNative() from Lodash
@@ -191,6 +199,25 @@ function describeID(id: DebugID): string {
199 return describeComponentFrame(name, element && element._source, ownerName);
200 }
201
202 +function describeFiber(fiber : Fiber) : string {
203 + switch (fiber.tag) {
204 + case IndeterminateComponent:
205 + case FunctionalComponent:
206 + case ClassComponent:
207 + case HostComponent:
208 + var owner = fiber._debugOwner;
209 + var source = fiber._debugSource;
210 + var name = getComponentName(fiber);
211 + var ownerName = null;
212 + if (owner) {
213 + ownerName = getComponentName(owner);
214 + }
215 + return describeComponentFrame(name, source, ownerName);
216 + default:
217 + return '';
218 + }
219 +}
220 +
221 var ReactComponentTreeHook = {
222 onSetChildren(id: DebugID, nextChildIDs: Array<DebugID>): void {
223 var item = getItem(id);
@@ -324,9 +351,15 @@ var ReactComponentTreeHook = {
351 }
352
353 var currentOwner = ReactCurrentOwner.current;
327 - if (currentOwner && typeof currentOwner._debugID === 'number') {
328 - var id = currentOwner && currentOwner._debugID;
329 - info += ReactComponentTreeHook.getStackAddendumByID(id);
354 + if (currentOwner) {
355 + if (typeof currentOwner.tag === 'number') {
356 + const workInProgress = ((currentOwner : any) : Fiber);
357 + // Safe because if current owner exists, we are reconciling,
358 + // and it is guaranteed to be the work-in-progress version.
359 + info += ReactComponentTreeHook.getStackAddendumByWorkInProgressFiber(workInProgress);
360 + } else if (typeof currentOwner._debugID === 'number') {
361 + info += ReactComponentTreeHook.getStackAddendumByID(currentOwner._debugID);
362 + }
363 }
364 return info;
365 },
@@ -340,6 +373,20 @@ var ReactComponentTreeHook = {
373 return info;
374 },
375
376 + // This function can only be called with a work-in-progress fiber and
377 + // only during begin or complete phase. Do not call it under any other
378 + // circumstances.
379 + getStackAddendumByWorkInProgressFiber(workInProgress : Fiber) : string {
380 + var info = '';
381 + var node = workInProgress;
382 + do {
383 + info += describeFiber(node);
384 + // Otherwise this return pointer might point to the wrong tree:
385 + node = node.return;
386 + } while (node);
387 + return info;
388 + },
389 +
390 getChildIDs(id: DebugID): Array<DebugID> {
391 var item = getItem(id);
392 return item ? item.childIDs : [];
src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
+41 -3
@@ -15,6 +15,7 @@
15 // of dynamic errors when using JSX with Flow.
16
17 var React;
18 +var ReactDOM;
19 var ReactTestUtils;
20
21 describe('ReactJSXElementValidator', () => {
@@ -25,6 +26,7 @@ describe('ReactJSXElementValidator', () => {
26 jest.resetModuleRegistry();
27
28 React = require('React');
29 + ReactDOM = require('ReactDOM');
30 ReactTestUtils = require('ReactTestUtils');
31
32 Component = class extends React.Component {
@@ -173,9 +175,6 @@ describe('ReactJSXElementValidator', () => {
175 expectDev(console.error.calls.count()).toBe(0);
176 });
177
176 - // TODO: These warnings currently come from the composite component, but
177 - // they should be moved into the ReactElementValidator.
178 -
178 it('should give context for PropType errors in nested components.', () => {
179 // In this test, we're making sure that if a proptype error is found in a
180 // component, we give a small hint as to which parent instantiated that
@@ -206,6 +205,45 @@ describe('ReactJSXElementValidator', () => {
205 );
206 });
207
208 + it('should update component stack after receiving next element', () => {
209 + spyOn(console, 'error');
210 + function MyComp() {
211 + return null;
212 + }
213 + MyComp.propTypes = {
214 + color: React.PropTypes.string,
215 + };
216 + function MiddleComp(props) {
217 + return <MyComp color={props.color} />;
218 + }
219 + function ParentComp(props) {
220 + if (props.warn) {
221 + // This element has a source thanks to JSX.
222 + return <MiddleComp color={42} />;
223 + }
224 + // This element has no source.
225 + return React.createElement(MiddleComp, {color: 'blue'});
226 + }
227 +
228 + var container = document.createElement('div');
229 + ReactDOM.render(<ParentComp warn={false} />, container);
230 + ReactDOM.render(<ParentComp warn={true} />, container);
231 +
232 + expect(console.error.calls.count()).toBe(1);
233 + // The warning should have the full stack with line numbers.
234 + // If it doesn't, it means we're using information from the old element.
235 + expect(
236 + console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)')
237 + ).toBe(
238 + 'Warning: Failed prop type: ' +
239 + 'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
240 + 'expected `string`.\n' +
241 + ' in MyComp (at **)\n' +
242 + ' in MiddleComp (at **)\n' +
243 + ' in ParentComp (at **)'
244 + );
245 + });
246 +
247 it('gives a helpful error when passing null, undefined, or boolean', () => {
248 var Undefined = undefined;
249 var Null = null;
src/renderers/dom/fiber/ReactDOMFiberComponent.js
+28 -14
@@ -20,21 +20,30 @@ var DOMProperty = require('DOMProperty');
20 var DOMPropertyOperations = require('DOMPropertyOperations');
21 var EventPluginRegistry = require('EventPluginRegistry');
22 var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
23 -var ReactDOMComponentTree = require('ReactDOMComponentTree');
23 var ReactDOMFiberInput = require('ReactDOMFiberInput');
24 var ReactDOMFiberOption = require('ReactDOMFiberOption');
25 var ReactDOMFiberSelect = require('ReactDOMFiberSelect');
26 var ReactDOMFiberTextarea = require('ReactDOMFiberTextarea');
27 +var { getCurrentFiberOwnerName } = require('ReactDebugCurrentFiber');
28
29 var emptyFunction = require('emptyFunction');
30 var focusNode = require('focusNode');
31 -var getCurrentOwnerName = require('getCurrentOwnerName');
31 var invariant = require('invariant');
32 var isEventSupported = require('isEventSupported');
33 var setInnerHTML = require('setInnerHTML');
34 var setTextContent = require('setTextContent');
35 var inputValueTracking = require('inputValueTracking');
36 var warning = require('warning');
37 +
38 +if (__DEV__) {
39 + var ReactDOMInvalidARIAHook = require('ReactDOMInvalidARIAHook');
40 + var ReactDOMNullInputValuePropHook = require('ReactDOMNullInputValuePropHook');
41 + var ReactDOMUnknownPropertyHook = require('ReactDOMUnknownPropertyHook');
42 + var { validateProperties: validateARIAProperties } = ReactDOMInvalidARIAHook;
43 + var { validateProperties: validateInputPropertes } = ReactDOMNullInputValuePropHook;
44 + var { validateProperties: validateUnknownPropertes } = ReactDOMUnknownPropertyHook;
45 +}
46 +
47 var didWarnShadyDOM = false;
48
49 var listenTo = ReactBrowserEventEmitter.listenTo;
@@ -56,8 +65,9 @@ var DOC_FRAGMENT_TYPE = 11;
65
66
67 function getDeclarationErrorAddendum() {
59 - var ownerName = getCurrentOwnerName();
68 + var ownerName = getCurrentFiberOwnerName();
69 if (ownerName) {
70 + // TODO: also report the stack.
71 return ' This DOM node was rendered by `' + ownerName + '`.';
72 }
73 return '';
@@ -122,6 +132,14 @@ function assertValidProps(tag : string, props : ?Object) {
132 );
133 }
134
135 +if (__DEV__) {
136 + var validatePropertiesInDevelopment = function(type, props) {
137 + validateARIAProperties(type, props);
138 + validateInputPropertes(type, props);
139 + validateUnknownPropertes(type, props);
140 + };
141 +}
142 +
143 function ensureListeningTo(rootContainerElement, registrationName) {
144 if (__DEV__) {
145 // IE8 has no API for event capturing and the `onScroll` event doesn't
@@ -424,19 +442,10 @@ function updateDOMProperties(
442 }
443 }
444 if (styleUpdates) {
427 - var componentPlaceholder = null;
428 - if (__DEV__) {
429 - // HACK
430 - var internalInstance = ReactDOMComponentTree.getInstanceFromNode(domElement);
431 - componentPlaceholder = {
432 - _currentElement: { type: internalInstance.type, props: internalInstance.memoizedProps },
433 - _debugID: internalInstance._debugID,
434 - };
435 - }
445 + // TODO: call ReactInstrumentation.debugTool.onHostOperation in DEV.
446 CSSPropertyOperations.setValueForStyles(
447 domElement,
448 styleUpdates,
439 - componentPlaceholder // TODO: Change CSSPropertyOperations to use getCurrentOwnerName.
449 );
450 }
451 }
@@ -524,12 +533,13 @@ var ReactDOMFiberComponent = {
533
534 var isCustomComponentTag = isCustomComponent(tag, rawProps);
535 if (__DEV__) {
536 + validatePropertiesInDevelopment(tag, rawProps);
537 if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
538 warning(
539 false,
540 '%s is using shady DOM. Using shady DOM with React can ' +
541 'cause things to break subtly.',
532 - getCurrentOwnerName() || 'A component'
542 + getCurrentFiberOwnerName() || 'A component'
543 );
544 didWarnShadyDOM = true;
545 }
@@ -641,6 +651,10 @@ var ReactDOMFiberComponent = {
651 nextRawProps : Object,
652 rootContainerElement : Element
653 ) : void {
654 + if (__DEV__) {
655 + validatePropertiesInDevelopment(tag, nextRawProps);
656 + }
657 +
658 var lastProps : Object;
659 var nextProps : Object;
660 switch (tag) {
src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js
+6 -6
@@ -23,8 +23,8 @@ type InputWithWrapperState = HTMLInputElement & {
23 var DOMPropertyOperations = require('DOMPropertyOperations');
24 var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
25 var ReactDOMComponentTree = require('ReactDOMComponentTree');
26 +var { getCurrentFiberOwnerName } = require('ReactDebugCurrentFiber');
27
27 -var getCurrentOwnerName = require('getCurrentOwnerName');
28 var invariant = require('invariant');
29 var warning = require('warning');
30
@@ -86,7 +86,7 @@ var ReactDOMInput = {
86 ReactControlledValuePropTypes.checkPropTypes(
87 'input',
88 props,
89 - getCurrentOwnerName()
89 + getCurrentFiberOwnerName()
90 );
91
92 if (
@@ -102,7 +102,7 @@ var ReactDOMInput = {
102 'both). Decide between using a controlled or uncontrolled input ' +
103 'element and remove one of these props. More info: ' +
104 'https://fb.me/react-controlled-components',
105 - getCurrentOwnerName() || 'A component',
105 + getCurrentFiberOwnerName() || 'A component',
106 props.type
107 );
108 didWarnCheckedDefaultChecked = true;
@@ -120,7 +120,7 @@ var ReactDOMInput = {
120 'both). Decide between using a controlled or uncontrolled input ' +
121 'element and remove one of these props. More info: ' +
122 'https://fb.me/react-controlled-components',
123 - getCurrentOwnerName() || 'A component',
123 + getCurrentFiberOwnerName() || 'A component',
124 props.type
125 );
126 didWarnValueDefaultValue = true;
@@ -151,7 +151,7 @@ var ReactDOMInput = {
151 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
152 'Decide between using a controlled or uncontrolled input ' +
153 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components',
154 - getCurrentOwnerName() || 'A component',
154 + getCurrentFiberOwnerName() || 'A component',
155 props.type
156 );
157 didWarnUncontrolledToControlled = true;
@@ -163,7 +163,7 @@ var ReactDOMInput = {
163 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
164 'Decide between using a controlled or uncontrolled input ' +
165 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components',
166 - getCurrentOwnerName() || 'A component',
166 + getCurrentFiberOwnerName() || 'A component',
167 props.type
168 );
169 didWarnControlledToUncontrolled = true;
src/renderers/dom/fiber/wrappers/ReactDOMFiberSelect.js
+3 -4
@@ -20,14 +20,13 @@ type SelectWithWrapperState = HTMLSelectElement & {
20 };
21
22 var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
23 -
24 -var getCurrentOwnerName = require('getCurrentOwnerName');
23 +var { getCurrentFiberOwnerName } = require('ReactDebugCurrentFiber');
24 var warning = require('warning');
25
26 var didWarnValueDefaultValue = false;
27
28 function getDeclarationErrorAddendum() {
30 - var ownerName = getCurrentOwnerName();
29 + var ownerName = getCurrentFiberOwnerName();
30 if (ownerName) {
31 return ' Check the render method of `' + ownerName + '`.';
32 }
@@ -43,7 +42,7 @@ function checkSelectPropTypes(props) {
42 ReactControlledValuePropTypes.checkPropTypes(
43 'select',
44 props,
46 - getCurrentOwnerName()
45 + getCurrentFiberOwnerName()
46 );
47
48 for (var i = 0; i < valuePropNames.length; i++) {
src/renderers/dom/fiber/wrappers/ReactDOMFiberTextarea.js
+2 -2
@@ -19,8 +19,8 @@ type TextAreaWithWrapperState = HTMLTextAreaElement & {
19 };
20
21 var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
22 +var { getCurrentFiberOwnerName } = require('ReactDebugCurrentFiber');
23
23 -var getCurrentOwnerName = require('getCurrentOwnerName');
24 var invariant = require('invariant');
25 var warning = require('warning');
26
@@ -69,7 +69,7 @@ var ReactDOMTextarea = {
69 ReactControlledValuePropTypes.checkPropTypes(
70 'textarea',
71 props,
72 - getCurrentOwnerName()
72 + getCurrentFiberOwnerName()
73 );
74 if (
75 props.value !== undefined &&
src/renderers/dom/shared/CSSPropertyOperations.js
+16 -14
@@ -13,7 +13,6 @@
13
14 var CSSProperty = require('CSSProperty');
15 var ExecutionEnvironment = require('ExecutionEnvironment');
16 -var ReactInstrumentation = require('ReactInstrumentation');
16
17 var camelizeStyleName = require('camelizeStyleName');
18 var dangerousStyleValue = require('dangerousStyleValue');
@@ -22,6 +21,10 @@ var hyphenateStyleName = require('hyphenateStyleName');
21 var memoizeStringOnly = require('memoizeStringOnly');
22 var warning = require('warning');
23
24 +if (__DEV__) {
25 + var { getCurrentFiberOwnerName } = require('ReactDebugCurrentFiber');
26 +}
27 +
28 var processStyleName = memoizeStringOnly(function(styleName) {
29 return hyphenateStyleName(styleName);
30 });
@@ -114,11 +117,18 @@ if (__DEV__) {
117 };
118
119 var checkRenderMessage = function(owner) {
117 - if (owner) {
118 - var name = getComponentName(owner);
119 - if (name) {
120 - return ' Check the render method of `' + name + '`.';
121 - }
120 + var ownerName;
121 + if (owner != null) {
122 + // Stack passes the owner manually all the way to CSSPropertyOperations.
123 + ownerName = getComponentName(owner);
124 + } else {
125 + // Fiber doesn't pass it but uses ReactDebugCurrentFiber to track it.
126 + // It is only enabled in development and tracks host components too.
127 + ownerName = getCurrentFiberOwnerName();
128 + // TODO: also report the stack.
129 + }
130 + if (ownerName) {
131 + return ' Check the render method of `' + ownerName + '`.';
132 }
133 return '';
134 };
@@ -193,14 +203,6 @@ var CSSPropertyOperations = {
203 * @param {ReactDOMComponent} component
204 */
205 setValueForStyles: function(node, styles, component) {
196 - if (__DEV__) {
197 - ReactInstrumentation.debugTool.onHostOperation({
198 - instanceID: component._debugID,
199 - type: 'update styles',
200 - payload: styles,
201 - });
202 - }
203 -
206 var style = node.style;
207 for (var styleName in styles) {
208 if (!styles.hasOwnProperty(styleName)) {
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
+164 -5
@@ -848,6 +848,33 @@ describe('ReactDOMComponent', () => {
848 );
849 });
850
851 + it('should include owner rather than parent in warnings', () => {
852 + var container = document.createElement('div');
853 +
854 + function Parent(props) {
855 + return props.children;
856 + }
857 + function Owner() {
858 + // We're using the input dangerouslySetInnerHTML invariant but the
859 + // exact error doesn't matter as long as we have a way to verify
860 + // that warnings and invariants contain owner rather than parent name.
861 + return (
862 + <Parent>
863 + <input dangerouslySetInnerHTML={{__html: 'content'}} />
864 + </Parent>
865 + );
866 + }
867 +
868 + expect(function() {
869 + ReactDOM.render(
870 + <Owner />,
871 + container
872 + );
873 + }).toThrowError(
874 + 'This DOM node was rendered by `Owner`.'
875 + );
876 + });
877 +
878 it('should emit a warning once for a named custom component using shady DOM', () => {
879 if (ReactDOMFeatureFlags.useCreateElement) {
880 spyOn(console, 'error');
@@ -1021,7 +1048,7 @@ describe('ReactDOMComponent', () => {
1048 ReactDOM.render(<X />, container);
1049 }).toThrowError(
1050 'input is a void element tag and must neither have `children` ' +
1024 - 'nor use `dangerouslySetInnerHTML`. Check the render method of X.'
1051 + 'nor use `dangerouslySetInnerHTML`. This DOM node was rendered by `X`.'
1052 );
1053 });
1054
@@ -1408,6 +1435,13 @@ describe('ReactDOMComponent', () => {
1435 });
1436
1437 it('should warn about class', () => {
1438 + spyOn(console, 'error');
1439 + ReactTestUtils.renderIntoDocument(React.createElement('div', {class: 'muffins'}));
1440 + expectDev(console.error.calls.count()).toBe(1);
1441 + expectDev(console.error.calls.argsFor(0)[0]).toContain('className');
1442 + });
1443 +
1444 + it('should warn about class (ssr)', () => {
1445 spyOn(console, 'error');
1446 ReactDOMServer.renderToString(React.createElement('div', {class: 'muffins'}));
1447 expectDev(console.error.calls.count()).toBe(1);
@@ -1426,7 +1460,37 @@ describe('ReactDOMComponent', () => {
1460 expectDev(console.error.calls.count()).toBe(2);
1461 });
1462
1463 + it('should warn about props that are no longer supported (ssr)', () => {
1464 + spyOn(console, 'error');
1465 + ReactDOMServer.renderToString(<div />);
1466 + expectDev(console.error.calls.count()).toBe(0);
1467 +
1468 + ReactDOMServer.renderToString(<div onFocusIn={() => {}} />);
1469 + expectDev(console.error.calls.count()).toBe(1);
1470 +
1471 + ReactDOMServer.renderToString(<div onFocusOut={() => {}} />);
1472 + expectDev(console.error.calls.count()).toBe(2);
1473 + });
1474 +
1475 it('gives source code refs for unknown prop warning', () => {
1476 + spyOn(console, 'error');
1477 + ReactTestUtils.renderIntoDocument(<div class="paladin"/>);
1478 + ReactTestUtils.renderIntoDocument(<input type="text" onclick="1"/>);
1479 + expectDev(console.error.calls.count()).toBe(2);
1480 + expect(
1481 + normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])
1482 + ).toBe(
1483 + 'Warning: Unknown DOM property class. Did you mean className?\n in div (at **)'
1484 + );
1485 + expect(
1486 + normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])
1487 + ).toBe(
1488 + 'Warning: Unknown event handler property onclick. Did you mean ' +
1489 + '`onClick`?\n in input (at **)'
1490 + );
1491 + });
1492 +
1493 + it('gives source code refs for unknown prop warning (ssr)', () => {
1494 spyOn(console, 'error');
1495 ReactDOMServer.renderToString(<div class="paladin"/>);
1496 ReactDOMServer.renderToString(<input type="text" onclick="1"/>);
@@ -1448,20 +1512,47 @@ describe('ReactDOMComponent', () => {
1512 spyOn(console, 'error');
1513 var container = document.createElement('div');
1514
1451 - ReactDOMServer.renderToString(<div className="paladin" />, container);
1515 + ReactTestUtils.renderIntoDocument(<div className="paladin" />, container);
1516 expectDev(console.error.calls.count()).toBe(0);
1517
1454 - ReactDOMServer.renderToString(<div class="paladin" />, container);
1518 + ReactTestUtils.renderIntoDocument(<div class="paladin" />, container);
1519 expectDev(console.error.calls.count()).toBe(1);
1520 expect(
1521 normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])
1522 ).toBe(
1523 'Warning: Unknown DOM property class. Did you mean className?\n in div (at **)'
1524 );
1525 + });
1526 +
1527 + it('gives source code refs for unknown prop warning for exact elements', () => {
1528 + spyOn(console, 'error');
1529
1530 + ReactTestUtils.renderIntoDocument(
1531 + <div className="foo1">
1532 + <div class="foo2"/>
1533 + <div onClick="foo3"/>
1534 + <div onclick="foo4"/>
1535 + <div className="foo5"/>
1536 + <div className="foo6"/>
1537 + </div>
1538 + );
1539 +
1540 + expectDev(console.error.calls.count()).toBe(2);
1541 +
1542 + expectDev(console.error.calls.argsFor(0)[0]).toContain('className');
1543 + var matches = console.error.calls.argsFor(0)[0].match(/.*\(.*:(\d+)\).*/);
1544 + var previousLine = matches[1];
1545 +
1546 + expectDev(console.error.calls.argsFor(1)[0]).toContain('onClick');
1547 + matches = console.error.calls.argsFor(1)[0].match(/.*\(.*:(\d+)\).*/);
1548 + var currentLine = matches[1];
1549 +
1550 + //verify line number has a proper relative difference,
1551 + //since hard coding the line number would make test too brittle
1552 + expect(parseInt(previousLine, 10) + 2).toBe(parseInt(currentLine, 10));
1553 });
1554
1464 - it('gives source code refs for unknown prop warning for exact elements ', () => {
1555 + it('gives source code refs for unknown prop warning for exact elements (ssr)', () => {
1556 spyOn(console, 'error');
1557
1558 ReactDOMServer.renderToString(
@@ -1489,7 +1580,58 @@ describe('ReactDOMComponent', () => {
1580 expect(parseInt(previousLine, 10) + 2).toBe(parseInt(currentLine, 10));
1581 });
1582
1492 - it('gives source code refs for unknown prop warning for exact elements in composition ', () => {
1583 + it('gives source code refs for unknown prop warning for exact elements in composition', () => {
1584 + spyOn(console, 'error');
1585 + var container = document.createElement('div');
1586 +
1587 + class Parent extends React.Component {
1588 + render() {
1589 + return <div><Child1 /><Child2 /><Child3 /><Child4 /></div>;
1590 + }
1591 + }
1592 +
1593 + class Child1 extends React.Component {
1594 + render() {
1595 + return <div class="paladin">Child1</div>;
1596 + }
1597 + }
1598 +
1599 + class Child2 extends React.Component {
1600 + render() {
1601 + return <div>Child2</div>;
1602 + }
1603 + }
1604 +
1605 + class Child3 extends React.Component {
1606 + render() {
1607 + return <div onclick="1">Child3</div>;
1608 + }
1609 + }
1610 +
1611 + class Child4 extends React.Component {
1612 + render() {
1613 + return <div>Child4</div>;
1614 + }
1615 + }
1616 +
1617 + ReactTestUtils.renderIntoDocument(<Parent />, container);
1618 +
1619 + expectDev(console.error.calls.count()).toBe(2);
1620 +
1621 + expectDev(console.error.calls.argsFor(0)[0]).toContain('className');
1622 + var matches = console.error.calls.argsFor(0)[0].match(/.*\(.*:(\d+)\).*/);
1623 + var previousLine = matches[1];
1624 +
1625 + expectDev(console.error.calls.argsFor(1)[0]).toContain('onClick');
1626 + matches = console.error.calls.argsFor(1)[0].match(/.*\(.*:(\d+)\).*/);
1627 + var currentLine = matches[1];
1628 +
1629 + //verify line number has a proper relative difference,
1630 + //since hard coding the line number would make test too brittle
1631 + expect(parseInt(previousLine, 10) + 12).toBe(parseInt(currentLine, 10));
1632 + });
1633 +
1634 + it('gives source code refs for unknown prop warning for exact elements in composition (ssr)', () => {
1635 spyOn(console, 'error');
1636 var container = document.createElement('div');
1637
@@ -1556,5 +1698,22 @@ describe('ReactDOMComponent', () => {
1698 'Warning: Unknown DOM property autofocus. Did you mean autoFocus?\n in input'
1699 );
1700 });
1701 +
1702 + it('should suggest property name if available (ssr)', () => {
1703 + spyOn(console, 'error');
1704 +
1705 + ReactDOMServer.renderToString(React.createElement('label', {for: 'test'}));
1706 + ReactDOMServer.renderToString(React.createElement('input', {type: 'text', autofocus: true}));
1707 +
1708 + expectDev(console.error.calls.count()).toBe(2);
1709 +
1710 + expectDev(console.error.calls.argsFor(0)[0]).toBe(
1711 + 'Warning: Unknown DOM property for. Did you mean htmlFor?\n in label'
1712 + );
1713 +
1714 + expectDev(console.error.calls.argsFor(1)[0]).toBe(
1715 + 'Warning: Unknown DOM property autofocus. Did you mean autoFocus?\n in input'
1716 + );
1717 + });
1718 });
1719 });
src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js
+29 -19
@@ -13,12 +13,23 @@
13
14 var DOMProperty = require('DOMProperty');
15 var ReactComponentTreeHook = require('ReactComponentTreeHook');
16 +var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
17
18 var warning = require('warning');
19
20 var warnedProperties = {};
21 var rARIA = new RegExp('^(aria)-[' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$');
22
23 +function getStackAddendum(debugID) {
24 + if (debugID != null) {
25 + // This can only happen on Stack
26 + return ReactComponentTreeHook.getStackAddendumByID(debugID);
27 + } else {
28 + // This can only happen on Fiber
29 + return ReactDebugCurrentFiber.getCurrentFiberStackAddendum();
30 + }
31 +}
32 +
33 function validateProperty(tagName, name, debugID) {
34 if (
35 warnedProperties.hasOwnProperty(name)
@@ -47,7 +58,7 @@ function validateProperty(tagName, name, debugID) {
58 'Unknown ARIA attribute %s. Did you mean %s?%s',
59 name,
60 standardName,
50 - ReactComponentTreeHook.getStackAddendumByID(debugID)
61 + getStackAddendum(debugID)
62 );
63 warnedProperties[name] = true;
64 return true;
@@ -57,11 +68,11 @@ function validateProperty(tagName, name, debugID) {
68 return true;
69 }
70
60 -function warnInvalidARIAProps(debugID, element) {
71 +function warnInvalidARIAProps(type, props, debugID) {
72 const invalidProps = [];
73
63 - for (var key in element.props) {
64 - var isValid = validateProperty(element.type, key, debugID);
74 + for (var key in props) {
75 + var isValid = validateProperty(type, key, debugID);
76 if (!isValid) {
77 invalidProps.push(key);
78 }
@@ -77,8 +88,8 @@ function warnInvalidARIAProps(debugID, element) {
88 'Invalid aria prop %s on <%s> tag. ' +
89 'For details, see https://fb.me/invalid-aria-prop%s',
90 unknownPropString,
80 - element.type,
81 - ReactComponentTreeHook.getStackAddendumByID(debugID)
91 + type,
92 + getStackAddendum(debugID)
93 );
94 } else if (invalidProps.length > 1) {
95 warning(
@@ -86,32 +97,31 @@ function warnInvalidARIAProps(debugID, element) {
97 'Invalid aria props %s on <%s> tag. ' +
98 'For details, see https://fb.me/invalid-aria-prop%s',
99 unknownPropString,
89 - element.type,
90 - ReactComponentTreeHook.getStackAddendumByID(debugID)
100 + type,
101 + getStackAddendum(debugID)
102 );
103 }
104 }
105
95 -function handleElement(debugID, element) {
96 - if (element == null || typeof element.type !== 'string') {
106 +function validateProperties(type, props, debugID /* Stack only */) {
107 + if (type.indexOf('-') >= 0 || props.is) {
108 return;
109 }
99 - if (element.type.indexOf('-') >= 0 || element.props.is) {
100 - return;
101 - }
102 -
103 - warnInvalidARIAProps(debugID, element);
110 + warnInvalidARIAProps(type, props, debugID);
111 }
112
113 var ReactDOMInvalidARIAHook = {
114 + // Fiber
115 + validateProperties,
116 + // Stack
117 onBeforeMountComponent(debugID, element) {
108 - if (__DEV__) {
109 - handleElement(debugID, element);
118 + if (__DEV__ && element != null && typeof element.type === 'string') {
119 + validateProperties(element.type, element.props, debugID);
120 }
121 },
122 onBeforeUpdateComponent(debugID, element) {
113 - if (__DEV__) {
114 - handleElement(debugID, element);
123 + if (__DEV__ && element != null && typeof element.type === 'string') {
124 + validateProperties(element.type, element.props, debugID);
125 }
126 },
127 };
src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js
+24 -9
@@ -12,26 +12,34 @@
12 'use strict';
13
14 var ReactComponentTreeHook = require('ReactComponentTreeHook');
15 +var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
16
17 var warning = require('warning');
18
19 var didWarnValueNull = false;
20
20 -function handleElement(debugID, element) {
21 - if (element == null) {
22 - return;
21 +function getStackAddendum(debugID) {
22 + if (debugID != null) {
23 + // This can only happen on Stack
24 + return ReactComponentTreeHook.getStackAddendumByID(debugID);
25 + } else {
26 + // This can only happen on Fiber
27 + return ReactDebugCurrentFiber.getCurrentFiberStackAddendum();
28 }
24 - if (element.type !== 'input' && element.type !== 'textarea' && element.type !== 'select') {
29 +}
30 +
31 +function validateProperties(type, props, debugID /* Stack only */) {
32 + if (type !== 'input' && type !== 'textarea' && type !== 'select') {
33 return;
34 }
27 - if (element.props != null && element.props.value === null && !didWarnValueNull) {
35 + if (props != null && props.value === null && !didWarnValueNull) {
36 warning(
37 false,
38 '`value` prop on `%s` should not be null. ' +
39 'Consider using the empty string to clear the component or `undefined` ' +
40 'for uncontrolled components.%s',
33 - element.type,
34 - ReactComponentTreeHook.getStackAddendumByID(debugID)
41 + type,
42 + getStackAddendum(debugID)
43 );
44
45 didWarnValueNull = true;
@@ -39,11 +47,18 @@ function handleElement(debugID, element) {
47 }
48
49 var ReactDOMNullInputValuePropHook = {
50 + // Fiber
51 + validateProperties,
52 + // Stack
53 onBeforeMountComponent(debugID, element) {
43 - handleElement(debugID, element);
54 + if (__DEV__ && element != null && typeof element.type === 'string') {
55 + validateProperties(element.type, element.props, debugID);
56 + }
57 },
58 onBeforeUpdateComponent(debugID, element) {
46 - handleElement(debugID, element);
59 + if (__DEV__ && element != null && typeof element.type === 'string') {
60 + validateProperties(element.type, element.props, debugID);
61 + }
62 },
63 };
64
src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js
+32 -17
@@ -14,9 +14,20 @@
14 var DOMProperty = require('DOMProperty');
15 var EventPluginRegistry = require('EventPluginRegistry');
16 var ReactComponentTreeHook = require('ReactComponentTreeHook');
17 +var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
18
19 var warning = require('warning');
20
21 +function getStackAddendum(debugID) {
22 + if (debugID != null) {
23 + // This can only happen on Stack
24 + return ReactComponentTreeHook.getStackAddendumByID(debugID);
25 + } else {
26 + // This can only happen on Fiber
27 + return ReactDebugCurrentFiber.getCurrentFiberStackAddendum();
28 + }
29 +}
30 +
31 if (__DEV__) {
32 var reactProps = {
33 children: true,
@@ -71,7 +82,7 @@ if (__DEV__) {
82 'Unknown DOM property %s. Did you mean %s?%s',
83 name,
84 standardName,
74 - ReactComponentTreeHook.getStackAddendumByID(debugID)
85 + getStackAddendum(debugID)
86 );
87 return true;
88 } else if (registrationName != null) {
@@ -80,7 +91,7 @@ if (__DEV__) {
91 'Unknown event handler property %s. Did you mean `%s`?%s',
92 name,
93 registrationName,
83 - ReactComponentTreeHook.getStackAddendumByID(debugID)
94 + getStackAddendum(debugID)
95 );
96 return true;
97 } else {
@@ -93,10 +104,10 @@ if (__DEV__) {
104 };
105 }
106
96 -var warnUnknownProperties = function(debugID, element) {
107 +var warnUnknownProperties = function(type, props, debugID) {
108 var unknownProps = [];
98 - for (var key in element.props) {
99 - var isValid = validateProperty(element.type, key, debugID);
109 + for (var key in props) {
110 + var isValid = validateProperty(type, key, debugID);
111 if (!isValid) {
112 unknownProps.push(key);
113 }
@@ -112,8 +123,8 @@ var warnUnknownProperties = function(debugID, element) {
123 'Unknown prop %s on <%s> tag. Remove this prop from the element. ' +
124 'For details, see https://fb.me/react-unknown-prop%s',
125 unknownPropString,
115 - element.type,
116 - ReactComponentTreeHook.getStackAddendumByID(debugID)
126 + type,
127 + getStackAddendum(debugID)
128 );
129 } else if (unknownProps.length > 1) {
130 warning(
@@ -121,28 +132,32 @@ var warnUnknownProperties = function(debugID, element) {
132 'Unknown props %s on <%s> tag. Remove these props from the element. ' +
133 'For details, see https://fb.me/react-unknown-prop%s',
134 unknownPropString,
124 - element.type,
125 - ReactComponentTreeHook.getStackAddendumByID(debugID)
135 + type,
136 + getStackAddendum(debugID)
137 );
138 }
139 };
140
130 -function handleElement(debugID, element) {
131 - if (element == null || typeof element.type !== 'string') {
141 +function validateProperties(type, props, debugID /* Stack only */) {
142 + if (type.indexOf('-') >= 0 || props.is) {
143 return;
144 }
134 - if (element.type.indexOf('-') >= 0 || element.props.is) {
135 - return;
136 - }
137 - warnUnknownProperties(debugID, element);
145 + warnUnknownProperties(type, props, debugID);
146 }
147
148 var ReactDOMUnknownPropertyHook = {
149 + // Fiber
150 + validateProperties,
151 + // Stack
152 onBeforeMountComponent(debugID, element) {
142 - handleElement(debugID, element);
153 + if (__DEV__ && element != null && typeof element.type === 'string') {
154 + validateProperties(element.type, element.props, debugID);
155 + }
156 },
157 onBeforeUpdateComponent(debugID, element) {
145 - handleElement(debugID, element);
158 + if (__DEV__ && element != null && typeof element.type === 'string') {
159 + validateProperties(element.type, element.props, debugID);
160 + }
161 },
162 };
163
src/renderers/dom/stack/client/ReactDOMComponent.js
+8 -4
@@ -88,10 +88,7 @@ function assertValidProps(component, props) {
88 '%s is a void element tag and must neither have `children` nor ' +
89 'use `dangerouslySetInnerHTML`.%s',
90 component._tag,
91 - component._currentElement._owner ?
92 - ' Check the render method of ' +
93 - component._currentElement._owner.getName() + '.' :
94 - ''
91 + getDeclarationErrorAddendum(component)
92 );
93 }
94 if (props.dangerouslySetInnerHTML != null) {
@@ -1029,6 +1026,13 @@ ReactDOMComponent.Mixin = {
1026 }
1027 }
1028 if (styleUpdates) {
1029 + if (__DEV__) {
1030 + ReactInstrumentation.debugTool.onHostOperation({
1031 + instanceID: this._debugID,
1032 + type: 'update styles',
1033 + payload: styleUpdates,
1034 + });
1035 + }
1036 CSSPropertyOperations.setValueForStyles(
1037 getNode(this),
1038 styleUpdates,
src/renderers/shared/fiber/ReactChildFiber.js
+96 -11
@@ -12,6 +12,7 @@
12
13 'use strict';
14
15 +import type { ReactElement } from 'ReactElementType';
16 import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
17 import type { ReactPortal } from 'ReactPortal';
18 import type { Fiber } from 'ReactFiber';
@@ -36,6 +37,11 @@ var emptyObject = require('emptyObject');
37 var getIteratorFn = require('getIteratorFn');
38 var invariant = require('invariant');
39
40 +if (__DEV__) {
41 + var { getCurrentFiberStackAddendum } = require('ReactDebugCurrentFiber');
42 + var warning = require('warning');
43 +}
44 +
45 const {
46 cloneFiber,
47 createFiberFromElement,
@@ -68,7 +74,7 @@ const {
74 Deletion,
75 } = ReactTypeOfSideEffect;
76
71 -function coerceRef(current: ?Fiber, element: ReactElement<any>) {
77 +function coerceRef(current: ?Fiber, element: ReactElement) {
78 let mixedRef = element.ref;
79 if (mixedRef != null && typeof mixedRef !== 'function') {
80 if (element._owner) {
@@ -256,7 +262,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
262 function updateElement(
263 returnFiber : Fiber,
264 current : ?Fiber,
259 - element : ReactElement<any>,
265 + element : ReactElement,
266 priority : PriorityLevel
267 ) : Fiber {
268 if (current == null || current.type !== element.type) {
@@ -271,6 +277,10 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
277 existing.ref = coerceRef(current, element);
278 existing.pendingProps = element.props;
279 existing.return = returnFiber;
280 + if (__DEV__) {
281 + existing._debugSource = element._source;
282 + existing._debugOwner = element._owner;
283 + }
284 return existing;
285 }
286 }
@@ -536,6 +546,48 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
546 return null;
547 }
548
549 + function warnOnDuplicateKey(
550 + child : mixed,
551 + knownKeys : Set<string> | null
552 + ) : Set<string> | null {
553 + if (__DEV__) {
554 + if (typeof child !== 'object' || child == null) {
555 + return knownKeys;
556 + }
557 + switch (child.$$typeof) {
558 + case REACT_ELEMENT_TYPE:
559 + case REACT_COROUTINE_TYPE:
560 + case REACT_YIELD_TYPE:
561 + case REACT_PORTAL_TYPE:
562 + const key = child.key;
563 + if (typeof key !== 'string') {
564 + break;
565 + }
566 + if (knownKeys == null) {
567 + knownKeys = new Set();
568 + knownKeys.add(key);
569 + break;
570 + }
571 + if (!knownKeys.has(key)) {
572 + knownKeys.add(key);
573 + break;
574 + }
575 + warning(
576 + false,
577 + 'Encountered two children with the same key, ' +
578 + '`%s`. Child keys must be unique; when two children share a key, ' +
579 + 'only the first child will be used.%s',
580 + key,
581 + getCurrentFiberStackAddendum()
582 + );
583 + break;
584 + default:
585 + break;
586 + }
587 + }
588 + return knownKeys;
589 + }
590 +
591 function reconcileChildrenArray(
592 returnFiber : Fiber,
593 currentFirstChild : ?Fiber,
@@ -561,6 +613,15 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
613 // If you change this code, also update reconcileChildrenIterator() which
614 // uses the same algorithm.
615
616 + if (__DEV__) {
617 + // First, validate keys.
618 + let knownKeys = null;
619 + for (let i = 0; i < newChildren.length; i++) {
620 + const child = newChildren[i];
621 + knownKeys = warnOnDuplicateKey(child, knownKeys);
622 + }
623 + }
624 +
625 let resultingFirstChild : ?Fiber = null;
626 let previousNewFiber : ?Fiber = null;
627
@@ -691,12 +752,37 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
752 function reconcileChildrenIterator(
753 returnFiber : Fiber,
754 currentFirstChild : ?Fiber,
694 - newChildren : Iterator<*>,
755 + newChildrenIterable : Iterable<*>,
756 priority : PriorityLevel) : ?Fiber {
757
758 // This is the same implementation as reconcileChildrenArray(),
759 // but using the iterator instead.
760
761 + const iteratorFn = getIteratorFn(newChildrenIterable);
762 + if (typeof iteratorFn !== 'function') {
763 + throw new Error('An object is not an iterable.');
764 + }
765 +
766 + if (__DEV__) {
767 + // First, validate keys.
768 + // We'll get a different iterator later for the main pass.
769 + const newChildren = iteratorFn.call(newChildrenIterable);
770 + if (newChildren == null) {
771 + throw new Error('An iterable object provided no iterator.');
772 + }
773 + let knownKeys = null;
774 + let step = newChildren.next();
775 + for (; !step.done; step = newChildren.next()) {
776 + const child = step.value;
777 + knownKeys = warnOnDuplicateKey(child, knownKeys);
778 + }
779 + }
780 +
781 + const newChildren = iteratorFn.call(newChildrenIterable);
782 + if (newChildren == null) {
783 + throw new Error('An iterable object provided no iterator.');
784 + }
785 +
786 let resultingFirstChild : ?Fiber = null;
787 let previousNewFiber : ?Fiber = null;
788
@@ -854,7 +940,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
940 function reconcileSingleElement(
941 returnFiber : Fiber,
942 currentFirstChild : ?Fiber,
857 - element : ReactElement<any>,
943 + element : ReactElement,
944 priority : PriorityLevel
945 ) : Fiber {
946 const key = element.key;
@@ -869,6 +955,10 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
955 existing.ref = coerceRef(child, element);
956 existing.pendingProps = element.props;
957 existing.return = returnFiber;
958 + if (__DEV__) {
959 + existing._debugSource = element._source;
960 + existing._debugOwner = element._owner;
961 + }
962 return existing;
963 } else {
964 deleteRemainingChildren(returnFiber, child);
@@ -1061,16 +1151,11 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
1151 );
1152 }
1153
1064 - const iteratorFn = getIteratorFn(newChild);
1065 - if (iteratorFn) {
1066 - const iterator = iteratorFn.call(newChild);
1067 - if (iterator == null) {
1068 - throw new Error('An iterable object provided no iterator.');
1069 - }
1154 + if (getIteratorFn(newChild)) {
1155 return reconcileChildrenIterator(
1156 returnFiber,
1157 currentFirstChild,
1073 - iterator,
1158 + newChild,
1159 priority
1160 );
1161 }
src/renderers/shared/fiber/ReactDebugCurrentFiber.js new
+55
@@ -0,0 +1,55 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactDebugCurrentFiber
10 + * @flow
11 + */
12 +
13 +'use strict';
14 +
15 +import type { Fiber } from 'ReactFiber';
16 +
17 +if (__DEV__) {
18 + var getComponentName = require('getComponentName');
19 + var { getStackAddendumByWorkInProgressFiber } = require('ReactComponentTreeHook');
20 +}
21 +
22 +function getCurrentFiberOwnerName() : string | null {
23 + if (__DEV__) {
24 + const fiber = ReactDebugCurrentFiber.current;
25 + if (fiber == null) {
26 + return null;
27 + }
28 + if (fiber._debugOwner == null) {
29 + return null;
30 + }
31 + return getComponentName(fiber._debugOwner);
32 + }
33 + return null;
34 +}
35 +
36 +function getCurrentFiberStackAddendum() : string | null {
37 + if (__DEV__) {
38 + const fiber = ReactDebugCurrentFiber.current;
39 + if (fiber == null) {
40 + return null;
41 + }
42 + // Safe because if current fiber exists, we are reconciling,
43 + // and it is guaranteed to be the work-in-progress version.
44 + return getStackAddendumByWorkInProgressFiber(fiber);
45 + }
46 + return null;
47 +}
48 +
49 +var ReactDebugCurrentFiber = {
50 + current: (null : Fiber | null),
51 + getCurrentFiberOwnerName,
52 + getCurrentFiberStackAddendum,
53 +};
54 +
55 +module.exports = ReactDebugCurrentFiber;
src/renderers/shared/fiber/ReactFiber.js
+27 -5
@@ -12,6 +12,8 @@
12
13 'use strict';
14
15 +import type { ReactElement, Source } from 'ReactElementType';
16 +import type { ReactInstance, DebugID } from 'ReactInstanceType';
17 import type { ReactFragment } from 'ReactTypes';
18 import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
19 import type { ReactPortal } from 'ReactPortal';
@@ -46,6 +48,11 @@ var invariant = require('invariant');
48 // A Fiber is work on a Component that needs to be done or was done. There can
49 // be more than one per component.
50 export type Fiber = {
51 + // __DEV__ only
52 + _debugID ?: DebugID,
53 + _debugSource ?: Source | null,
54 + _debugOwner ?: Fiber | ReactInstance | null, // Stack compatible
55 +
56 // These first fields are conceptually members of an Instance. This used to
57 // be split into a separate type and intersected with the other Fiber fields,
58 // but until Flow fixes its intersection bugs, we've merged them into a
@@ -145,7 +152,7 @@ export type Fiber = {
152 };
153
154 if (__DEV__) {
148 - var debugCounter = 0;
155 + var debugCounter = 1;
156 }
157
158 // This is a constructor of a POJO instead of a constructor function for a few
@@ -162,7 +169,7 @@ if (__DEV__) {
169 // 5) It should be easy to port this to a C struct and keep a C implementation
170 // compatible.
171 var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
165 - var fiber = {
172 + var fiber : Fiber = {
173
174 // Instance
175
@@ -204,9 +211,13 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
211 alternate: null,
212
213 };
214 +
215 if (__DEV__) {
208 - (fiber : any)._debugID = debugCounter++;
216 + fiber._debugID = debugCounter++;
217 + fiber._debugSource = null;
218 + fiber._debugOwner = null;
219 }
220 +
221 return fiber;
222 };
223
@@ -266,6 +277,12 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
277 alt.memoizedProps = fiber.memoizedProps;
278 alt.memoizedState = fiber.memoizedState;
279
280 + if (__DEV__) {
281 + alt._debugID = fiber._debugID;
282 + alt._debugSource = fiber._debugSource;
283 + alt._debugOwner = fiber._debugOwner;
284 + }
285 +
286 return alt;
287 };
288
@@ -274,11 +291,16 @@ exports.createHostRootFiber = function() : Fiber {
291 return fiber;
292 };
293
277 -exports.createFiberFromElement = function(element : ReactElement<*>, priorityLevel : PriorityLevel) : Fiber {
278 -// $FlowFixMe: ReactElement.key is currently defined as ?string but should be defined as null | string in Flow.
294 +exports.createFiberFromElement = function(element : ReactElement, priorityLevel : PriorityLevel) : Fiber {
295 const fiber = createFiberFromElementType(element.type, element.key);
296 fiber.pendingProps = element.props;
297 fiber.pendingWorkPriority = priorityLevel;
298 +
299 + if (__DEV__) {
300 + fiber._debugSource = element._source;
301 + fiber._debugOwner = element._owner;
302 + }
303 +
304 return fiber;
305 };
306
src/renderers/shared/fiber/ReactFiberBeginWork.js
+8
@@ -60,6 +60,10 @@ var {
60 var ReactCurrentOwner = require('ReactCurrentOwner');
61 var ReactFiberClassComponent = require('ReactFiberClassComponent');
62
63 +if (__DEV__) {
64 + var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
65 +}
66 +
67 module.exports = function<T, P, I, TI, C, CX>(
68 config : HostConfig<T, P, I, TI, C, CX>,
69 hostContext : HostContext<C, CX>,
@@ -452,6 +456,10 @@ module.exports = function<T, P, I, TI, C, CX>(
456 return bailoutOnLowPriority(current, workInProgress);
457 }
458
459 + if (__DEV__) {
460 + ReactDebugCurrentFiber.current = workInProgress;
461 + }
462 +
463 // If we don't bail out, we're going be recomputing our children so we need
464 // to drop our effect list.
465 workInProgress.firstEffect = null;
src/renderers/shared/fiber/ReactFiberClassComponent.js
+8
@@ -180,6 +180,14 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
180 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
181 name
182 );
183 + const hasMutatedProps = instance.props !== workInProgress.pendingProps;
184 + warning(
185 + instance.props === undefined || !hasMutatedProps,
186 + '%s(...): When calling super() in `%s`, make sure to pass ' +
187 + 'up the same props that your component\'s constructor was passed.',
188 + name,
189 + name
190 + );
191 }
192
193 const state = instance.state;
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+8
@@ -44,6 +44,10 @@ var {
44 Callback,
45 } = ReactTypeOfSideEffect;
46
47 +if (__DEV__) {
48 + var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
49 +}
50 +
51 module.exports = function<T, P, I, TI, C, CX>(
52 config : HostConfig<T, P, I, TI, C, CX>,
53 hostContext : HostContext<C, CX>,
@@ -167,6 +171,10 @@ module.exports = function<T, P, I, TI, C, CX>(
171 }
172
173 function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
174 + if (__DEV__) {
175 + ReactDebugCurrentFiber.current = workInProgress;
176 + }
177 +
178 switch (workInProgress.tag) {
179 case FunctionalComponent:
180 workInProgress.memoizedProps = workInProgress.pendingProps;
src/renderers/shared/fiber/ReactFiberContext.js
+15 -11
@@ -40,8 +40,8 @@ function getUnmaskedContext() {
40 return contextStack[index];
41 }
42
43 -exports.getMaskedContext = function(fiber : Fiber) {
44 - const type = fiber.type;
43 +exports.getMaskedContext = function(workInProgress : Fiber) {
44 + const type = workInProgress.type;
45 const contextTypes = type.contextTypes;
46 if (!contextTypes) {
47 return emptyObject;
@@ -54,9 +54,8 @@ exports.getMaskedContext = function(fiber : Fiber) {
54 }
55
56 if (__DEV__) {
57 - const name = getComponentName(fiber);
58 - const debugID = 0; // TODO: pass a real ID
59 - checkReactTypeSpec(contextTypes, context, 'context', name, null, debugID);
57 + const name = getComponentName(workInProgress);
58 + checkReactTypeSpec(contextTypes, context, 'context', name, null, workInProgress);
59 }
60
61 return context;
@@ -91,7 +90,7 @@ exports.pushTopLevelContextObject = function(context : Object, didChange : boole
90 didPerformWorkStack[index] = didChange;
91 };
92
94 -function processChildContext(fiber : Fiber, parentContext : Object): Object {
93 +function processChildContext(fiber : Fiber, parentContext : Object, isReconciling : boolean): Object {
94 const instance = fiber.stateNode;
95 const childContextTypes = fiber.type.childContextTypes;
96 const childContext = instance.getChildContext();
@@ -105,15 +104,20 @@ function processChildContext(fiber : Fiber, parentContext : Object): Object {
104 }
105 if (__DEV__) {
106 const name = getComponentName(fiber);
108 - const debugID = 0; // TODO: pass a real ID
109 - checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
107 + // We can only provide accurate element stacks if we pass work-in-progress tree
108 + // during the begin or complete phase. However currently this function is also
109 + // called from unstable_renderSubtree legacy implementation. In this case it unsafe to
110 + // assume anything about the given fiber. We won't pass it down if we aren't sure.
111 + // TODO: remove this hack when we delete unstable_renderSubtree in Fiber.
112 + const workInProgress = isReconciling ? fiber : null;
113 + checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, workInProgress);
114 }
115 return {...parentContext, ...childContext};
116 }
117 exports.processChildContext = processChildContext;
118
115 -exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) : void {
116 - const instance = fiber.stateNode;
119 +exports.pushContextProvider = function(workInProgress : Fiber, didPerformWork : boolean) : void {
120 + const instance = workInProgress.stateNode;
121 const memoizedMergedChildContext = instance.__reactInternalMemoizedMergedChildContext;
122 const canReuseMergedChildContext = !didPerformWork && memoizedMergedChildContext != null;
123
@@ -121,7 +125,7 @@ exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean)
125 if (canReuseMergedChildContext) {
126 mergedContext = memoizedMergedChildContext;
127 } else {
124 - mergedContext = processChildContext(fiber, getUnmaskedContext());
128 + mergedContext = processChildContext(workInProgress, getUnmaskedContext(), true);
129 instance.__reactInternalMemoizedMergedChildContext = mergedContext;
130 }
131
src/renderers/shared/fiber/ReactFiberReconciler.js
+1 -1
@@ -91,7 +91,7 @@ export type Reconciler<C, I, TI> = {
91 getContextForSubtree._injectFiber(function(fiber : Fiber) {
92 const parentContext = findCurrentUnmaskedContext(fiber);
93 return isContextProvider(fiber) ?
94 - processChildContext(fiber, parentContext) :
94 + processChildContext(fiber, parentContext, false) :
95 parentContext;
96 });
97
src/renderers/shared/fiber/ReactFiberScheduler.js
+18
@@ -59,6 +59,7 @@ var {
59
60 if (__DEV__) {
61 var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
62 + var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
63 }
64
65 var timeHeuristicForUnitOfWork = 1;
@@ -186,6 +187,10 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
187
188 function commitAllHostEffects(finishedWork : Fiber) {
189 while (nextEffect) {
190 + if (__DEV__) {
191 + ReactDebugCurrentFiber.current = nextEffect;
192 + }
193 +
194 if (nextEffect.effectTag & ContentReset) {
195 config.resetTextContent(nextEffect.stateNode);
196 }
@@ -233,6 +238,10 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
238 nextEffect = nextEffect.nextEffect;
239 }
240
241 + if (__DEV__) {
242 + ReactDebugCurrentFiber.current = null;
243 + }
244 +
245 // If the root itself had an effect, we perform that since it is
246 // not part of the effect list.
247 if (finishedWork.effectTag !== NoEffect) {
@@ -478,6 +487,9 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
487 }
488
489 ReactCurrentOwner.current = null;
490 + if (__DEV__) {
491 + ReactDebugCurrentFiber.current = null;
492 + }
493
494 return next;
495 }
@@ -511,6 +523,9 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
523 }
524
525 ReactCurrentOwner.current = null;
526 + if (__DEV__) {
527 + ReactDebugCurrentFiber.current = null;
528 + }
529
530 return next;
531 }
@@ -731,6 +746,9 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
746 function captureError(failedWork : ?Fiber, error : Error) : ?Fiber {
747 // It is no longer valid because we exited the user code.
748 ReactCurrentOwner.current = null;
749 + if (__DEV__) {
750 + ReactDebugCurrentFiber.current = null;
751 + }
752 // It is no longer valid because this unit of work failed.
753 nextUnitOfWork = null;
754
src/renderers/shared/fiber/getCurrentOwnerName.js deleted
-24
@@ -1,24 +0,0 @@
1 -/**
2 - * Copyright 2013-present, Facebook, Inc.
3 - * All rights reserved.
4 - *
5 - * This source code is licensed under the BSD-style license found in the
6 - * LICENSE file in the root directory of this source tree. An additional grant
7 - * of patent rights can be found in the PATENTS file in the same directory.
8 - *
9 - * @providesModule getCurrentOwnerName
10 - * @flow
11 - */
12 -
13 -'use strict';
14 -
15 -var ReactCurrentOwner = require('ReactCurrentOwner');
16 -var getComponentName = require('getComponentName');
17 -
18 -module.exports = function() : null | string {
19 - if (ReactCurrentOwner.current) {
20 - return getComponentName(ReactCurrentOwner.current);
21 - } else {
22 - return null;
23 - }
24 -};
src/renderers/shared/shared/__tests__/ReactChildReconciler-test.js
+73 -4
@@ -29,7 +29,25 @@ describe('ReactChildReconciler', () => {
29 ReactTestUtils = require('ReactTestUtils');
30 });
31
32 - it('warns for duplicated keys', () => {
32 + function createIterable(array) {
33 + return {
34 + '@@iterator': function() {
35 + var i = 0;
36 + return {
37 + next() {
38 + const next = {
39 + value: i < array.length ? array[i] : undefined,
40 + done: i === array.length,
41 + };
42 + i++;
43 + return next;
44 + },
45 + };
46 + },
47 + };
48 + }
49 +
50 + it('warns for duplicated array keys', () => {
51 spyOn(console, 'error');
52
53 class Component extends React.Component {
@@ -46,7 +64,7 @@ describe('ReactChildReconciler', () => {
64 );
65 });
66
49 - it('warns for duplicated keys with component stack info', () => {
67 + it('warns for duplicated array keys with component stack info', () => {
68 spyOn(console, 'error');
69
70 class Component extends React.Component {
@@ -70,8 +88,59 @@ describe('ReactChildReconciler', () => {
88 ReactTestUtils.renderIntoDocument(<GrandParent />);
89
90 expectDev(console.error.calls.count()).toBe(1);
73 - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
74 - 'Warning: flattenChildren(...): ' +
91 + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
92 + 'Encountered two children with the same key, `1`. ' +
93 + 'Child keys must be unique; when two children share a key, ' +
94 + 'only the first child will be used.\n' +
95 + ' in div (at **)\n' +
96 + ' in Component (at **)\n' +
97 + ' in Parent (at **)\n' +
98 + ' in GrandParent (at **)'
99 + );
100 + });
101 +
102 + it('warns for duplicated iterable keys', () => {
103 + spyOn(console, 'error');
104 +
105 + class Component extends React.Component {
106 + render() {
107 + return <div>{createIterable([<div key="1" />, <div key="1" />])}</div>;
108 + }
109 + }
110 +
111 + ReactTestUtils.renderIntoDocument(<Component />);
112 +
113 + expectDev(console.error.calls.count()).toBe(1);
114 + expectDev(console.error.calls.argsFor(0)[0]).toContain(
115 + 'Child keys must be unique; when two children share a key, only the first child will be used.'
116 + );
117 + });
118 +
119 + it('warns for duplicated iterable keys with component stack info', () => {
120 + spyOn(console, 'error');
121 +
122 + class Component extends React.Component {
123 + render() {
124 + return <div>{createIterable([<div key="1" />, <div key="1" />])}</div>;
125 + }
126 + }
127 +
128 + class Parent extends React.Component {
129 + render() {
130 + return React.cloneElement(this.props.child);
131 + }
132 + }
133 +
134 + class GrandParent extends React.Component {
135 + render() {
136 + return <Parent child={<Component />} />;
137 + }
138 + }
139 +
140 + ReactTestUtils.renderIntoDocument(<GrandParent />);
141 +
142 + expectDev(console.error.calls.count()).toBe(1);
143 + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
144 'Encountered two children with the same key, `1`. ' +
145 'Child keys must be unique; when two children share a key, ' +
146 'only the first child will be used.\n' +
src/renderers/shared/shared/__tests__/ReactMultiChild-test.js
+65 -3
@@ -154,7 +154,7 @@ describe('ReactMultiChild', () => {
154 expect(mockUnmount.mock.calls.length).toBe(1);
155 });
156
157 - it('should warn for duplicated keys with component stack info', () => {
157 + it('should warn for duplicated array keys with component stack info', () => {
158 spyOn(console, 'error');
159
160 var container = document.createElement('div');
@@ -188,8 +188,70 @@ describe('ReactMultiChild', () => {
188 );
189
190 expectDev(console.error.calls.count()).toBe(1);
191 - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
192 - 'Warning: flattenChildren(...): ' +
191 + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
192 + 'Encountered two children with the same key, `1`. ' +
193 + 'Child keys must be unique; when two children share a key, ' +
194 + 'only the first child will be used.\n' +
195 + ' in div (at **)\n' +
196 + ' in WrapperComponent (at **)\n' +
197 + ' in div (at **)\n' +
198 + ' in Parent (at **)'
199 + );
200 + });
201 +
202 + it('should warn for duplicated iterable keys with component stack info', () => {
203 + spyOn(console, 'error');
204 +
205 + var container = document.createElement('div');
206 +
207 + class WrapperComponent extends React.Component {
208 + render() {
209 + return <div>{this.props.children}</div>;
210 + }
211 + }
212 +
213 + class Parent extends React.Component {
214 + render() {
215 + return (
216 + <div>
217 + <WrapperComponent>
218 + {this.props.children}
219 + </WrapperComponent>
220 + </div>
221 + );
222 + }
223 + }
224 +
225 + function createIterable(array) {
226 + return {
227 + '@@iterator': function() {
228 + var i = 0;
229 + return {
230 + next() {
231 + const next = {
232 + value: i < array.length ? array[i] : undefined,
233 + done: i === array.length,
234 + };
235 + i++;
236 + return next;
237 + },
238 + };
239 + },
240 + };
241 + }
242 +
243 + ReactDOM.render(
244 + <Parent>{createIterable([<div key="1"/>])}</Parent>,
245 + container
246 + );
247 +
248 + ReactDOM.render(
249 + <Parent>{createIterable([<div key="1"/>, <div key="1"/>])}</Parent>,
250 + container
251 + );
252 +
253 + expectDev(console.error.calls.count()).toBe(1);
254 + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
255 'Encountered two children with the same key, `1`. ' +
256 'Child keys must be unique; when two children share a key, ' +
257 'only the first child will be used.\n' +
src/renderers/shared/shared/__tests__/refs-test.js
+17 -12
@@ -12,6 +12,7 @@
12 'use strict';
13
14 var React = require('React');
15 +var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
16 var ReactTestUtils = require('ReactTestUtils');
17
18 /**
@@ -334,12 +335,14 @@ describe('string refs between fiber and stack', () => {
335 ReactDOMFiber.unmountComponentAtNode(container);
336 expect(a.refs.span).toBe(undefined);
337 expect(layerMounted).toBe(true);
337 - expectDev(console.error.calls.count()).toBe(1);
338 - expectDev(console.error.calls.argsFor(0)[0]).toBe(
339 - 'Warning: You are using React DOM Fiber which is an experimental ' +
340 - 'renderer. It is likely to have bugs, breaking changes and is ' +
341 - 'unsupported.'
342 - );
338 + if (!ReactDOMFeatureFlags.useFiber) {
339 + expectDev(console.error.calls.count()).toBe(1);
340 + expectDev(console.error.calls.argsFor(0)[0]).toBe(
341 + 'Warning: You are using React DOM Fiber which is an experimental ' +
342 + 'renderer. It is likely to have bugs, breaking changes and is ' +
343 + 'unsupported.'
344 + );
345 + }
346 });
347
348 it('attaches, detaches from stack component with fiber layer', () => {
@@ -379,11 +382,13 @@ describe('string refs between fiber and stack', () => {
382 ReactDOM.unmountComponentAtNode(container);
383 expect(a.refs.span).toBe(undefined);
384 expect(layerMounted).toBe(true);
382 - expectDev(console.error.calls.count()).toBe(1);
383 - expectDev(console.error.calls.argsFor(0)[0]).toBe(
384 - 'Warning: You are using React DOM Fiber which is an experimental ' +
385 - 'renderer. It is likely to have bugs, breaking changes and is ' +
386 - 'unsupported.'
387 - );
385 + if (!ReactDOMFeatureFlags.useFiber) {
386 + expectDev(console.error.calls.count()).toBe(1);
387 + expectDev(console.error.calls.argsFor(0)[0]).toBe(
388 + 'Warning: You are using React DOM Fiber which is an experimental ' +
389 + 'renderer. It is likely to have bugs, breaking changes and is ' +
390 + 'unsupported.'
391 + );
392 + }
393 });
394 });
src/shared/ReactTypeOfWork.js renamed
src/shared/types/checkReactTypeSpec.js
+16 -4
@@ -44,7 +44,7 @@ var loggedTypeFailures = {};
44 * @param {string} location e.g. "prop", "context", "child context"
45 * @param {string} componentName Name of the component for error messages.
46 * @param {?object} element The React element that is being type-checked
47 - * @param {?number} debugID The React component instance that is being type-checked
47 + * @param {?number} workInProgressOrDebugID The React component instance that is being type-checked
48 * @private
49 */
50 function checkReactTypeSpec(
@@ -53,7 +53,9 @@ function checkReactTypeSpec(
53 location: ReactPropTypeLocations,
54 componentName,
55 element,
56 - debugID,
56 + // It is only safe to pass fiber if it is the work-in-progress version, and
57 + // only during reconciliation (begin and complete phase).
58 + workInProgressOrDebugID,
59 ) {
60 for (var typeSpecName in typeSpecs) {
61 if (typeSpecs.hasOwnProperty(typeSpecName)) {
@@ -99,8 +101,18 @@ function checkReactTypeSpec(
101 if (!ReactComponentTreeHook) {
102 ReactComponentTreeHook = require('ReactComponentTreeHook');
103 }
102 - if (debugID !== null) {
103 - componentStackInfo = ReactComponentTreeHook.getStackAddendumByID(debugID);
104 + if (workInProgressOrDebugID != null) {
105 + if (typeof workInProgressOrDebugID === 'number') {
106 + // DebugID from Stack.
107 + const debugID = workInProgressOrDebugID;
108 + componentStackInfo = ReactComponentTreeHook.getStackAddendumByID(debugID);
109 + } else if (typeof workInProgressOrDebugID.tag === 'number') {
110 + // This is a Fiber.
111 + // The stack will only be correct if this is a work in progress
112 + // version and we're calling it during reconciliation.
113 + const workInProgress = workInProgressOrDebugID;
114 + componentStackInfo = ReactComponentTreeHook.getStackAddendumByWorkInProgressFiber(workInProgress);
115 + }
116 } else if (element !== null) {
117 componentStackInfo = ReactComponentTreeHook.getCurrentStackAddendum(element);
118 }