@samitouri / QOS-React / commits / 6abf025d46

Make createFragment return an array, disallow objects as children

Ben Alpert committed Aug 24, 2015 at 16:07 UTC 6abf025d4656abcc90e2ed71c66793377fbba233
15 files changed +186 -525
src/addons/ReactFragment.js
+38 -132
@@ -13,6 +13,8 @@
13
14 var ReactElement = require('ReactElement');
15
16 +var invariant = require('invariant');
17 +var traverseAllChildren = require('traverseAllChildren');
18 var warning = require('warning');
19
20 /**
@@ -20,86 +22,26 @@ var warning = require('warning');
22 * or nested sets. This allowed us a way to explicitly key a set a fragment of
23 * components. This is now being replaced with an opaque data structure.
24 * The upgrade path is to call React.addons.createFragment({ key: value }) to
23 - * create a keyed fragment. The resulting data structure is opaque, for now.
25 + * create a keyed fragment. The resulting data structure is an array.
26 */
27
26 -var fragmentKey;
27 -var didWarnKey;
28 -var canWarnForReactFragment;
28 +var numericPropertyRegex = /^\d+$/;
29
30 -if (__DEV__) {
31 - fragmentKey = '_reactFragment';
32 - didWarnKey = '_reactDidWarn';
33 -
34 - try {
35 - // Feature test. Don't even try to issue this warning if we can't use
36 - // enumerable: false.
37 -
38 - var dummy = function() {
39 - return 1;
40 - };
41 -
42 - Object.defineProperty(
43 - {},
44 - fragmentKey,
45 - {enumerable: false, value: true}
46 - );
47 -
48 - Object.defineProperty(
49 - {},
50 - 'key',
51 - {enumerable: true, get: dummy}
52 - );
30 +var userProvidedKeyEscapeRegex = /\//g;
31 +function escapeUserProvidedKey(text) {
32 + return ('' + text).replace(userProvidedKeyEscapeRegex, '//');
33 +}
34
54 - canWarnForReactFragment = true;
55 - } catch (x) {
56 - canWarnForReactFragment = false;
35 +function processSingleChildWithContext(ctx, child, childKey) {
36 + if (ReactElement.isValidElement(child)) {
37 + child = ReactElement.cloneAndReplaceKey(child, ctx.prefix + childKey);
38 }
58 -
59 - var proxyPropertyAccessWithWarning = function(obj, key) {
60 - Object.defineProperty(obj, key, {
61 - enumerable: true,
62 - get: function() {
63 - warning(
64 - this[didWarnKey],
65 - 'A ReactFragment is an opaque type. Accessing any of its ' +
66 - 'properties is deprecated. Pass it to one of the React.Children ' +
67 - 'helpers.'
68 - );
69 - this[didWarnKey] = true;
70 - return this[fragmentKey][key];
71 - },
72 - set: function(value) {
73 - warning(
74 - this[didWarnKey],
75 - 'A ReactFragment is an immutable opaque type. Mutating its ' +
76 - 'properties is deprecated.'
77 - );
78 - this[didWarnKey] = true;
79 - this[fragmentKey][key] = value;
80 - },
81 - });
82 - };
83 -
84 - var issuedWarnings = {};
85 -
86 - var getFragmentKeyString = function(fragment) {
87 - var fragmentCacheKey = '';
88 - for (var key in fragment) {
89 - fragmentCacheKey += key + ':' + (typeof fragment[key]) + ',';
90 - }
91 - return fragmentCacheKey;
92 - };
93 -
94 - var didWarnForFragment = function(fragmentCacheKey) {
95 - // We use the keys and the type of the value as a heuristic to dedupe the
96 - // warning to avoid spamming too much.
97 - var alreadyWarnedOnce = !!issuedWarnings[fragmentCacheKey];
98 - issuedWarnings[fragmentCacheKey] = true;
99 - return alreadyWarnedOnce;
100 - };
39 + // For text components, leave unkeyed
40 + ctx.result.push(child);
41 }
42
43 +var warnedAboutNumeric = false;
44 +
45 var ReactFragment = {
46 // Wrap a keyed object in an opaque proxy that warns you if you access any
47 // of its properties.
@@ -121,71 +63,35 @@ var ReactFragment = {
63 );
64 return object;
65 }
124 - if (canWarnForReactFragment) {
125 - var proxy = {};
126 - Object.defineProperty(proxy, fragmentKey, {
127 - enumerable: false,
128 - value: object,
129 - });
130 - Object.defineProperty(proxy, didWarnKey, {
131 - writable: true,
132 - enumerable: false,
133 - value: false,
134 - });
135 - for (var key in object) {
136 - proxyPropertyAccessWithWarning(proxy, key);
137 - }
138 - Object.preventExtensions(proxy);
139 - return proxy;
140 - }
66 }
142 - return object;
143 - },
144 - // Extract the original keyed object from the fragment opaque type. Warn if
145 - // a plain object is passed here.
146 - extract: function(fragment) {
147 - if (__DEV__) {
148 - if (canWarnForReactFragment) {
149 - if (!fragment[fragmentKey]) {
150 - var fragmentKeys = getFragmentKeyString(fragment);
67 + invariant(
68 + object.nodeType !== 1,
69 + 'React.addons.createFragment(...): Encountered an invalid child; DOM ' +
70 + 'elements are not valid children of React components.'
71 + );
72 +
73 + var result = [];
74 + var context = {
75 + result: result,
76 + prefix: '',
77 + };
78 +
79 + for (var key in object) {
80 + if (__DEV__) {
81 + if (!warnedAboutNumeric && numericPropertyRegex.test(key)) {
82 warning(
152 - didWarnForFragment(fragmentKeys),
153 - 'Any use of a keyed object should be wrapped in ' +
154 - 'React.addons.createFragment(object) before being passed as a ' +
155 - 'child. {%s}',
156 - fragmentKeys
83 + false,
84 + 'React.addons.createFragment(...): Child objects should have ' +
85 + 'non-numeric keys so ordering is preserved.'
86 );
158 - return fragment;
159 - }
160 - return fragment[fragmentKey];
161 - }
162 - }
163 - return fragment;
164 - },
165 - // Check if this is a fragment and if so, extract the keyed object. If it
166 - // is a fragment-like object, warn that it should be wrapped. Ignore if we
167 - // can't determine what kind of object this is.
168 - extractIfFragment: function(fragment) {
169 - if (__DEV__) {
170 - if (canWarnForReactFragment) {
171 - // If it is the opaque type, return the keyed object.
172 - if (fragment[fragmentKey]) {
173 - return fragment[fragmentKey];
174 - }
175 - // Otherwise, check each property if it has an element, if it does
176 - // it is probably meant as a fragment, so we can warn early. Defer,
177 - // the warning to extract.
178 - for (var key in fragment) {
179 - if (fragment.hasOwnProperty(key) &&
180 - ReactElement.isValidElement(fragment[key])) {
181 - // This looks like a fragment object, we should provide an
182 - // early warning.
183 - return ReactFragment.extract(fragment);
184 - }
87 + warnedAboutNumeric = true;
88 }
89 }
90 + context.prefix = escapeUserProvidedKey(key) + '/';
91 + traverseAllChildren(object[key], processSingleChildWithContext, context);
92 }
188 - return fragment;
93 +
94 + return result;
95 },
96 };
97
src/addons/__tests__/ReactFragment-test.js
+19 -51
@@ -23,51 +23,23 @@ describe('ReactFragment', function() {
23 ReactFragment = require('ReactFragment');
24 });
25
26 - it('should warn if a plain object is used as a child', function() {
27 - spyOn(console, 'error');
28 - var children = {
29 - x: <span />,
30 - y: <span />,
31 - };
32 - void <div>{children}</div>;
33 - expect(console.error.calls.length).toBe(1);
34 - expect(console.error.calls[0].args[0]).toContain(
35 - 'Any use of a keyed object'
36 - );
37 - expect(console.error.calls[0].args[0]).toContain(
38 - '{x:object,y:object,}'
39 - );
40 - // Only warn once for the same set of children
41 - var sameChildren = {
42 - x: <span />,
43 - y: <span />,
44 - };
45 - void <div>{sameChildren}</div>;
46 - expect(console.error.calls.length).toBe(1);
47 - });
48 -
49 - it('should warn if a plain object even if it is deep', function() {
50 - spyOn(console, 'error');
26 + it('should throw if a plain object is used as a child', function() {
27 var children = {
28 x: <span />,
29 y: <span />,
30 z: <span />,
31 };
32 var element = <div>{[children]}</div>;
57 - expect(console.error.calls.length).toBe(0);
33 var container = document.createElement('div');
59 - ReactDOM.render(element, container);
60 - expect(console.error.calls.length).toBe(1);
61 - expect(console.error.calls[0].args[0]).toContain(
62 - 'Any use of a keyed object'
63 - );
64 - expect(console.error.calls[0].args[0]).toContain(
65 - '{x:object,y:object,z:object,}'
34 + expect(() => ReactDOM.render(element, container)).toThrow(
35 + 'Invariant Violation: Objects are not valid as a React child (found ' +
36 + 'object with keys {x, y, z}). If you meant to render a collection of ' +
37 + 'children, use an array instead or wrap the object using ' +
38 + 'React.addons.createFragment(object).'
39 );
40 });
41
69 - it('should warn if a plain object even if it is in an owner', function() {
70 - spyOn(console, 'error');
42 + it('should throw if a plain object even if it is in an owner', function() {
43 class Foo {
44 render() {
45 var children = {
@@ -78,27 +50,23 @@ describe('ReactFragment', function() {
50 return <div>{[children]}</div>;
51 }
52 }
81 - expect(console.error.calls.length).toBe(0);
53 var container = document.createElement('div');
83 - ReactDOM.render(<Foo />, container);
84 - expect(console.error.calls.length).toBe(1);
85 - expect(console.error.calls[0].args[0]).toContain(
86 - 'Any use of a keyed object'
54 + expect(() => ReactDOM.render(<Foo />, container)).toThrow(
55 + 'Invariant Violation: Objects are not valid as a React child (found ' +
56 + 'object with keys {a, b, c}). If you meant to render a collection of ' +
57 + 'children, use an array instead or wrap the object using ' +
58 + 'React.addons.createFragment(object). Check the render method of `Foo`.'
59 );
60 });
61
90 - it('should warn if accessing any property on a fragment', function() {
62 + it('warns for numeric keys on objects as children', function() {
63 spyOn(console, 'error');
92 - var children = {
93 - x: <span />,
94 - y: <span />,
95 - };
96 - var frag = ReactFragment.create(children);
97 - void frag.x;
98 - frag.y = 10;
99 - expect(console.error.calls.length).toBe(1);
100 - expect(console.error.calls[0].args[0]).toContain(
101 - 'A ReactFragment is an opaque type'
64 +
65 + ReactFragment.create({1: <span />, 2: <span />});
66 +
67 + expect(console.error.argsForCall.length).toBe(1);
68 + expect(console.error.argsForCall[0][0]).toContain(
69 + 'Child objects should have non-numeric keys so ordering is preserved.'
70 );
71 });
72
src/addons/transitions/ReactTransitionChildMapping.js
+3 -6
@@ -12,13 +12,12 @@
12
13 'use strict';
14
15 -var ReactChildren = require('ReactChildren');
16 -var ReactFragment = require('ReactFragment');
15 +var flattenChildren = require('flattenChildren');
16
17 var ReactTransitionChildMapping = {
18 /**
19 * Given `this.props.children`, return an object mapping key to child. Just
21 - * simple syntactic sugar around ReactChildren.map().
20 + * simple syntactic sugar around flattenChildren().
21 *
22 * @param {*} children `this.props.children`
23 * @return {object} Mapping of key to child
@@ -27,9 +26,7 @@ var ReactTransitionChildMapping = {
26 if (!children) {
27 return children;
28 }
30 - return ReactFragment.extract(ReactChildren.map(children, function(child) {
31 - return child;
32 - }));
29 + return flattenChildren(children);
30 },
31
32 /**
src/isomorphic/children/ReactChildren.js
+1 -1
@@ -146,7 +146,7 @@ function countChildren(children, context) {
146 }
147
148
149 -function pushSingleChildToArray(traverseContext, child, name, i) {
149 +function pushSingleChildToArray(traverseContext, child, name) {
150 if (child == null) {
151 return;
152 }
src/isomorphic/children/__tests__/ReactChildren-test.js
+56 -120
@@ -22,20 +22,6 @@ describe('ReactChildren', function() {
22 ReactFragment = require('ReactFragment');
23 });
24
25 - function nthChild(mappedChildren, n) {
26 - var result = null;
27 - ReactChildren.forEach(mappedChildren, function(child, index) {
28 - if (index === n) {
29 - result = child;
30 - }
31 - });
32 - return result;
33 - }
34 -
35 - function keyOfNthChild(mappedChildren, n) {
36 - return Object.keys(ReactFragment.extract(mappedChildren))[n];
37 - }
38 -
25 it('should support identity for simple', function() {
26 var callback = jasmine.createSpy().andCallFake(function(kid, index) {
27 return kid;
@@ -52,7 +38,7 @@ describe('ReactChildren', function() {
38 callback.reset();
39 var mappedChildren = ReactChildren.map(instance.props.children, callback);
40 expect(callback).toHaveBeenCalledWith(simpleKid, 0);
55 - expect(nthChild(mappedChildren, 0)).toBe(simpleKid);
41 + expect(mappedChildren[0]).toEqual(<span key=".$simple/.$simple" />);
42 });
43
44 it('should treat single arrayless child as being in array', function() {
@@ -67,7 +53,7 @@ describe('ReactChildren', function() {
53 callback.reset();
54 var mappedChildren = ReactChildren.map(instance.props.children, callback);
55 expect(callback).toHaveBeenCalledWith(simpleKid, 0);
70 - expect(nthChild(mappedChildren, 0)).toBe(simpleKid);
56 + expect(mappedChildren[0]).toEqual(<span key=".0/.0" />);
57 });
58
59 it('should treat single child in array as expected', function() {
@@ -82,7 +68,7 @@ describe('ReactChildren', function() {
68 callback.reset();
69 var mappedChildren = ReactChildren.map(instance.props.children, callback);
70 expect(callback).toHaveBeenCalledWith(simpleKid, 0);
85 - expect(nthChild(mappedChildren, 0)).toBe(simpleKid);
71 + expect(mappedChildren[0]).toEqual(<span key=".$simple/.$simple" />);
72
73 });
74
@@ -97,9 +83,9 @@ describe('ReactChildren', function() {
83 var mappedChildren = ReactChildren.map(instance.props.children, mapFn);
84
85 expect(ReactChildren.count(mappedChildren)).toBe(1);
100 - expect(nthChild(mappedChildren, 0)).not.toBe(simpleKid);
101 - expect(nthChild(mappedChildren, 0).props.children).toBe(simpleKid);
102 - expect(keyOfNthChild(mappedChildren, 0)).toBe('.$simple');
86 + expect(mappedChildren[0]).not.toBe(simpleKid);
87 + expect(mappedChildren[0].props.children).toBe(simpleKid);
88 + expect(mappedChildren[0].key).toBe('.$simple/.0');
89 });
90
91 it('should invoke callback with the right context', function() {
@@ -121,7 +107,7 @@ describe('ReactChildren', function() {
107 ReactChildren.map(instance.props.children, callback, scopeTester);
108
109 expect(ReactChildren.count(mappedChildren)).toBe(1);
124 - expect(nthChild(mappedChildren, 0)).toBe(scopeTester);
110 + expect(mappedChildren[0]).toBe(scopeTester);
111 });
112
113 it('should be called for each child', function() {
@@ -165,35 +151,30 @@ describe('ReactChildren', function() {
151 var mappedChildren =
152 ReactChildren.map(instance.props.children, callback);
153 expect(callback.calls.length).toBe(5);
168 - expect(ReactChildren.count(mappedChildren)).toBe(5);
154 + expect(ReactChildren.count(mappedChildren)).toBe(4);
155 // Keys default to indices.
156 expect([
171 - keyOfNthChild(mappedChildren, 0),
172 - keyOfNthChild(mappedChildren, 1),
173 - keyOfNthChild(mappedChildren, 2),
174 - keyOfNthChild(mappedChildren, 3),
175 - keyOfNthChild(mappedChildren, 4),
157 + mappedChildren[0].key,
158 + mappedChildren[1].key,
159 + mappedChildren[2].key,
160 + mappedChildren[3].key,
161 ]).toEqual(
177 - ['.$keyZero', '.1', '.$keyTwo', '.3', '.$keyFour']
162 + ['.$keyZero/.$giraffe', '.$keyTwo/.0', '.3/.0', '.$keyFour/.$keyFour']
163 );
164
165 expect(callback).toHaveBeenCalledWith(zero, 0);
181 - expect(nthChild(mappedChildren, 0)).toBe(zeroMapped);
166 + expect(mappedChildren[0]).toEqual(<div key=".$keyZero/.$giraffe" />);
167
168 expect(callback).toHaveBeenCalledWith(one, 1);
184 - expect(nthChild(mappedChildren, 1)).toBe(oneMapped);
169 + expect(mappedChildren[1]).toEqual(<div key=".$keyTwo/.0" />);
170
171 expect(callback).toHaveBeenCalledWith(two, 2);
187 - expect(nthChild(mappedChildren, 2)).toBe(twoMapped);
172 + expect(mappedChildren[2]).toEqual(<span key=".3/.0" />);
173
174 expect(callback).toHaveBeenCalledWith(three, 3);
190 - expect(nthChild(mappedChildren, 3)).toBe(threeMapped);
191 -
192 - expect(callback).toHaveBeenCalledWith(four, 4);
193 - expect(nthChild(mappedChildren, 4)).toBe(fourMapped);
175 + expect(mappedChildren[3]).toEqual(<div key=".$keyFour/.$keyFour" />);
176 });
177
196 -
178 it('should be called for each child in nested structure', function() {
179 var zero = <div key="keyZero" />;
180 var one = null;
@@ -222,64 +203,54 @@ describe('ReactChildren', function() {
203 index === 4 ? fourMapped : fiveMapped;
204 });
205
225 - var instance = (
226 - <div>{
227 - [
228 - ReactFragment.create({
229 - firstHalfKey: [zero, one, two],
230 - secondHalfKey: [three, four],
231 - keyFive: five,
232 - }),
233 - ]
234 - }</div>
235 - );
206 + var frag = ReactFragment.create({
207 + firstHalfKey: [zero, one, two],
208 + secondHalfKey: [three, four],
209 + keyFive: five,
210 + });
211 + var instance = <div>{[frag]}</div>;
212
213 ReactChildren.forEach(instance.props.children, callback);
238 - expect(callback).toHaveBeenCalledWith(zero, 0);
239 - expect(callback).toHaveBeenCalledWith(one, 1);
240 - expect(callback).toHaveBeenCalledWith(two, 2);
241 - expect(callback).toHaveBeenCalledWith(three, 3);
242 - expect(callback).toHaveBeenCalledWith(four, 4);
243 - expect(callback).toHaveBeenCalledWith(five, 5);
214 + expect(callback).toHaveBeenCalledWith(frag[0], 0);
215 + expect(callback).toHaveBeenCalledWith(frag[1], 1);
216 + expect(callback).toHaveBeenCalledWith(frag[2], 2);
217 + expect(callback).toHaveBeenCalledWith(frag[3], 3);
218 + expect(callback).toHaveBeenCalledWith(frag[4], 4);
219 + expect(callback).toHaveBeenCalledWith(frag[5], 5);
220 callback.reset();
221
222 var mappedChildren = ReactChildren.map(instance.props.children, callback);
223 expect(callback.calls.length).toBe(6);
248 - expect(ReactChildren.count(mappedChildren)).toBe(6);
224 + expect(ReactChildren.count(mappedChildren)).toBe(5);
225 // Keys default to indices.
226 expect([
251 - keyOfNthChild(mappedChildren, 0),
252 - keyOfNthChild(mappedChildren, 1),
253 - keyOfNthChild(mappedChildren, 2),
254 - keyOfNthChild(mappedChildren, 3),
255 - keyOfNthChild(mappedChildren, 4),
256 - keyOfNthChild(mappedChildren, 5),
227 + mappedChildren[0].key,
228 + mappedChildren[1].key,
229 + mappedChildren[2].key,
230 + mappedChildren[3].key,
231 + mappedChildren[4].key,
232 ]).toEqual([
258 - '.0:$firstHalfKey:0:$keyZero',
259 - '.0:$firstHalfKey:0:1',
260 - '.0:$firstHalfKey:0:$keyTwo',
261 - '.0:$secondHalfKey:0:0',
262 - '.0:$secondHalfKey:0:$keyFour',
263 - '.0:$keyFive:$keyFiveInner',
233 + '.0:$firstHalfKey//=1$keyZero/.$giraffe',
234 + '.0:$firstHalfKey//=1$keyTwo/.0',
235 + '.0:3/.0',
236 + '.0:$secondHalfKey//=1$keyFour/.$keyFour',
237 + '.0:$keyFive//=1$keyFiveInner/.0',
238 ]);
239
266 - expect(callback).toHaveBeenCalledWith(zero, 0);
267 - expect(nthChild(mappedChildren, 0)).toBe(zeroMapped);
240 + expect(callback).toHaveBeenCalledWith(frag[0], 0);
241 + expect(mappedChildren[0]).toEqual(<div key=".0:$firstHalfKey//=1$keyZero/.$giraffe" />);
242
269 - expect(callback).toHaveBeenCalledWith(one, 1);
270 - expect(nthChild(mappedChildren, 1)).toBe(oneMapped);
243 + expect(callback).toHaveBeenCalledWith(frag[1], 1);
244 + expect(mappedChildren[1]).toEqual(<div key=".0:$firstHalfKey//=1$keyTwo/.0" />);
245
272 - expect(callback).toHaveBeenCalledWith(two, 2);
273 - expect(nthChild(mappedChildren, 2)).toBe(twoMapped);
274 -
275 - expect(callback).toHaveBeenCalledWith(three, 3);
276 - expect(nthChild(mappedChildren, 3)).toBe(threeMapped);
246 + expect(callback).toHaveBeenCalledWith(frag[2], 2);
247 + expect(mappedChildren[2]).toEqual(<span key=".0:3/.0" />);
248
278 - expect(callback).toHaveBeenCalledWith(four, 4);
279 - expect(nthChild(mappedChildren, 4)).toBe(fourMapped);
249 + expect(callback).toHaveBeenCalledWith(frag[3], 3);
250 + expect(mappedChildren[3]).toEqual(<div key=".0:$secondHalfKey//=1$keyFour/.$keyFour" />);
251
281 - expect(callback).toHaveBeenCalledWith(five, 5);
282 - expect(nthChild(mappedChildren, 5)).toBe(fiveMapped);
252 + expect(callback).toHaveBeenCalledWith(frag[4], 4);
253 + expect(mappedChildren[4]).toEqual(<div key=".0:$keyFive//=1$keyFiveInner/.0" />);
254 });
255
256 it('should retain key across two mappings', function() {
@@ -302,20 +273,20 @@ describe('ReactChildren', function() {
273 </div>
274 );
275
305 - var expectedForcedKeys = ['.$keyZero', '.$keyOne'];
276 + var expectedForcedKeys = ['.$keyZero/.$giraffe', '.$keyOne/.0'];
277 var mappedChildrenForcedKeys =
278 ReactChildren.map(forcedKeys.props.children, mapFn);
308 - var mappedForcedKeys = Object.keys(mappedChildrenForcedKeys);
279 + var mappedForcedKeys = mappedChildrenForcedKeys.map((c) => c.key);
280 expect(mappedForcedKeys).toEqual(expectedForcedKeys);
281
282 var expectedRemappedForcedKeys = [
312 - '.$=1$keyZero:$giraffe',
313 - '.$=1$keyOne:0',
283 + '.$=1$keyZero//=1$giraffe/.$giraffe',
284 + '.$=1$keyOne//=10/.0',
285 ];
286 var remappedChildrenForcedKeys =
287 ReactChildren.map(mappedChildrenForcedKeys, mapFn);
288 expect(
318 - Object.keys(remappedChildrenForcedKeys)
289 + remappedChildrenForcedKeys.map((c) => c.key)
290 ).toEqual(expectedRemappedForcedKeys);
291
292 });
@@ -355,8 +326,7 @@ describe('ReactChildren', function() {
326 var mapped = ReactChildren.map(instance.props.children, mapFn);
327
328 expect(console.error.calls.length).toEqual(1);
358 - expect(nthChild(mapped, 0)).toBe(zero);
359 - expect(keyOfNthChild(mapped, 0)).toBe('.$something');
329 + expect(mapped[0]).toEqual(<div key=".$something/.$something" />);
330 });
331
332 it('should return 0 for null children', function() {
@@ -424,40 +394,6 @@ describe('ReactChildren', function() {
394 expect(numberOfChildren).toBe(6);
395 });
396
427 - it('should warn if a fragment is used without the wrapper', function() {
428 - spyOn(console, 'error');
429 - var child = React.createElement('span');
430 - ReactChildren.forEach({a: child, b: child}, function(c) {
431 - expect(c).toBe(child);
432 - });
433 - expect(console.error.calls.length).toBe(1);
434 - expect(console.error.calls[0].args[0]).toContain('use of a keyed object');
435 - });
436 -
437 - it('should warn if a fragment is accessed', function() {
438 - spyOn(console, 'error');
439 - var child = React.createElement('span');
440 - var frag = ReactChildren.map([child, child], function(c) {
441 - return c;
442 - });
443 - for (var key in frag) {
444 - void frag[key];
445 - break;
446 - }
447 - expect(console.error.calls.length).toBe(1);
448 - expect(console.error.calls[0].args[0]).toContain('is an opaque type');
449 -
450 - var frag2 = ReactChildren.map([child, child], function(c) {
451 - return c;
452 - });
453 - for (var key2 in frag2) {
454 - frag2[key2] = 123;
455 - break;
456 - }
457 - expect(console.error.calls.length).toBe(2);
458 - expect(console.error.calls[1].args[0]).toContain('is an immutable opaque');
459 - });
460 -
397 it('should flatten children to an array', function() {
398 expect(ReactChildren.toArray(undefined)).toEqual([]);
399 expect(ReactChildren.toArray(null)).toEqual([]);
src/isomorphic/children/__tests__/sliceChildren-test.js
+33 -25
@@ -14,22 +14,15 @@
14 describe('sliceChildren', function() {
15
16 var React;
17 - var ReactFragment;
17
18 var sliceChildren;
19
20 beforeEach(function() {
21 React = require('React');
23 - ReactFragment = require('ReactFragment');
22
23 sliceChildren = require('sliceChildren');
24 });
25
28 - function testKeyValuePairs(children, expectedPairs) {
29 - var obj = ReactFragment.extract(children);
30 - expect(obj).toEqual(expectedPairs);
31 - }
32 -
26 it('should render the whole set if start zero is supplied', function() {
27 var fullSet = [
28 <div key="A" />,
@@ -37,11 +30,11 @@ describe('sliceChildren', function() {
30 <div key="C" />,
31 ];
32 var children = sliceChildren(fullSet, 0);
40 - testKeyValuePairs(children, {
41 - '.$A': fullSet[0],
42 - '.$B': fullSet[1],
43 - '.$C': fullSet[2],
44 - });
33 + expect(children).toEqual([
34 + <div key=".$A" />,
35 + <div key=".$B" />,
36 + <div key=".$C" />,
37 + ]);
38 });
39
40 it('should render the remaining set if no end index is supplied', function() {
@@ -51,10 +44,10 @@ describe('sliceChildren', function() {
44 <div key="C" />,
45 ];
46 var children = sliceChildren(fullSet, 1);
54 - testKeyValuePairs(children, {
55 - '.$B': fullSet[1],
56 - '.$C': fullSet[2],
57 - });
47 + expect(children).toEqual([
48 + <div key=".$B" />,
49 + <div key=".$C" />,
50 + ]);
51 });
52
53 it('should exclude everything at or after the end index', function() {
@@ -65,21 +58,36 @@ describe('sliceChildren', function() {
58 <div key="D" />,
59 ];
60 var children = sliceChildren(fullSet, 1, 2);
68 - testKeyValuePairs(children, {
69 - '.$B': fullSet[1],
70 - });
61 + expect(children).toEqual([
62 + <div key=".$B" />,
63 + ]);
64 });
65
66 it('should allow static children to be sliced', function() {
74 - var a = <div />;
75 - var b = <div />;
76 - var c = <div />;
67 + var a = <a />;
68 + var b = <b />;
69 + var c = <i />;
70
71 var el = <div>{a}{b}{c}</div>;
72 var children = sliceChildren(el.props.children, 1, 2);
80 - testKeyValuePairs(children, {
81 - '.1': b,
82 - });
73 + expect(children).toEqual([
74 + <b key=".1" />,
75 + ]);
76 + });
77 +
78 + it('should slice nested children', function() {
79 + var fullSet = [
80 + <div key="A" />,
81 + [
82 + <div key="B" />,
83 + <div key="C" />,
84 + ],
85 + <div key="D" />,
86 + ];
87 + var children = sliceChildren(fullSet, 1, 2);
88 + expect(children).toEqual([
89 + <div key=".1:$B" />,
90 + ]);
91 });
92
93 });
src/isomorphic/children/sliceChildren.js
+3 -20
@@ -11,9 +11,7 @@
11
12 'use strict';
13
14 -var ReactFragment = require('ReactFragment');
15 -
16 -var flattenChildren = require('flattenChildren');
14 +var ReactChildren = require('ReactChildren');
15
16 /**
17 * Slice children that are typically specified as `props.children`. This version
@@ -29,23 +27,8 @@ function sliceChildren(children, start, end) {
27 return children;
28 }
29
32 - var slicedChildren = {};
33 - var flattenedMap = flattenChildren(children);
34 - var ii = 0;
35 - for (var key in flattenedMap) {
36 - if (!flattenedMap.hasOwnProperty(key)) {
37 - continue;
38 - }
39 - var child = flattenedMap[key];
40 - if (ii >= start) {
41 - slicedChildren[key] = child;
42 - }
43 - ii++;
44 - if (end != null && ii >= end) {
45 - break;
46 - }
47 - }
48 - return ReactFragment.create(slicedChildren);
30 + var array = ReactChildren.toArray(children);
31 + return array.slice(start, end);
32 }
33
34 module.exports = sliceChildren;
src/isomorphic/classic/element/ReactElementValidator.js
-38
@@ -19,7 +19,6 @@
19 'use strict';
20
21 var ReactElement = require('ReactElement');
22 -var ReactFragment = require('ReactFragment');
22 var ReactPropTypeLocations = require('ReactPropTypeLocations');
23 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
24 var ReactCurrentOwner = require('ReactCurrentOwner');
@@ -47,8 +46,6 @@ var ownerHasKeyUseWarning = {};
46
47 var loggedTypeFailures = {};
48
50 -var NUMERIC_PROPERTY_REGEX = /^\d+$/;
51 -
49 /**
50 * Warn if the element doesn't have an explicit key assigned to it.
51 * This element is in an array. The array could grow and shrink or be
@@ -80,34 +77,6 @@ function validateExplicitKey(element, parentType) {
77 );
78 }
79
83 -/**
84 - * Warn if the key is being defined as an object property but has an incorrect
85 - * value.
86 - *
87 - * @internal
88 - * @param {string} name Property name of the key.
89 - * @param {ReactElement} element Component that requires a key.
90 - * @param {*} parentType element's parent's type.
91 - */
92 -function validatePropertyKey(name, element, parentType) {
93 - if (!NUMERIC_PROPERTY_REGEX.test(name)) {
94 - return;
95 - }
96 - var addenda = getAddendaForKeyUse('numericKeys', element, parentType);
97 - if (addenda === null) {
98 - // we already showed the warning
99 - return;
100 - }
101 - warning(
102 - false,
103 - 'Child objects should have non-numeric keys so ordering is preserved.' +
104 - '%s%s%s',
105 - addenda.parentOrOwner || '',
106 - addenda.childOwner || '',
107 - addenda.url || ''
108 - );
109 -}
110 -
80 /**
81 * Shared warning and monitoring code for the key warnings.
82 *
@@ -189,13 +158,6 @@ function validateChildKeys(node, parentType) {
158 }
159 }
160 }
192 - } else if (typeof node === 'object') {
193 - var fragment = ReactFragment.extractIfFragment(node);
194 - for (var key in fragment) {
195 - if (fragment.hasOwnProperty(key)) {
196 - validatePropertyKey(key, fragment[key], parentType);
197 - }
198 - }
161 }
162 }
163 }
src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
-49
@@ -16,7 +16,6 @@
16
17 var React;
18 var ReactDOM;
19 -var ReactFragment;
19 var ReactTestUtils;
20
21 describe('ReactElementValidator', function() {
@@ -27,7 +26,6 @@ describe('ReactElementValidator', function() {
26
27 React = require('React');
28 ReactDOM = require('ReactDOM');
30 - ReactFragment = require('ReactFragment');
29 ReactTestUtils = require('ReactTestUtils');
30 ComponentClass = React.createClass({
31 render: function() {
@@ -36,10 +34,6 @@ describe('ReactElementValidator', function() {
34 });
35 });
36
39 - function frag(obj) {
40 - return ReactFragment.create(obj);
41 - }
42 -
37 it('warns for keys for arrays of elements in rest args', function() {
38 spyOn(console, 'error');
39 var Component = React.createFactory(ComponentClass);
@@ -205,41 +199,6 @@ describe('ReactElementValidator', function() {
199 expect(console.error.argsForCall.length).toBe(0);
200 });
201
208 - it('warns for numeric keys on objects in rest args', function() {
209 - spyOn(console, 'error');
210 - var Component = React.createFactory(ComponentClass);
211 -
212 - Component(null, frag({1: Component(), 2: Component()}));
213 -
214 - expect(console.error.argsForCall.length).toBe(1);
215 - expect(console.error.argsForCall[0][0]).toContain(
216 - 'Child objects should have non-numeric keys so ordering is preserved.'
217 - );
218 - });
219 -
220 - it('does not warn for numeric keys in entry iterables in rest args',
221 - function() {
222 - spyOn(console, 'error');
223 - var Component = React.createFactory(ComponentClass);
224 -
225 - var iterable = {
226 - '@@iterator': function() {
227 - var i = 0;
228 - return {
229 - next: function() {
230 - var done = ++i > 2;
231 - return {value: done ? undefined : [i, Component()], done: done};
232 - },
233 - };
234 - },
235 - };
236 - iterable.entries = iterable['@@iterator'];
237 -
238 - Component(null, iterable);
239 -
240 - expect(console.error.argsForCall.length).toBe(0);
241 - });
242 -
202 it('does not warn when the element is directly in rest args', function() {
203 spyOn(console, 'error');
204 var Component = React.createFactory(ComponentClass);
@@ -454,14 +413,6 @@ describe('ReactElementValidator', function() {
413 );
414 });
415
457 - it('should warn if a fragment is used without the wrapper', function() {
458 - spyOn(console, 'error');
459 - var child = React.createElement('span');
460 - React.createElement('div', null, {a: child, b: child});
461 - expect(console.error.calls.length).toBe(1);
462 - expect(console.error.calls[0].args[0]).toContain('use of a keyed object');
463 - });
464 -
416 it('should warn when accessing .type on an element factory', function() {
417 spyOn(console, 'error');
418 var TestComponent = React.createClass({
src/isomorphic/classic/types/ReactPropTypes.js
+1 -7
@@ -12,7 +12,6 @@
12 'use strict';
13
14 var ReactElement = require('ReactElement');
15 -var ReactFragment = require('ReactFragment');
15 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
16
17 var emptyFunction = require('emptyFunction');
@@ -371,12 +370,7 @@ function isNode(propValue) {
370 }
371 }
372 } else {
374 - propValue = ReactFragment.extractIfFragment(propValue);
375 - for (var k in propValue) {
376 - if (!isNode(propValue[k])) {
377 - return false;
378 - }
379 - }
373 + return false;
374 }
375
376 return true;
src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js
+1 -15
@@ -368,6 +368,7 @@ describe('ReactPropTypes', function() {
368 typeCheckFail(PropTypes.node, true, failMessage);
369 typeCheckFail(PropTypes.node, function() {}, failMessage);
370 typeCheckFail(PropTypes.node, {key: function() {}}, failMessage);
371 + typeCheckFail(PropTypes.node, {key: <div />}, failMessage);
372 });
373
374 it('should not warn for valid values', function() {
@@ -377,7 +378,6 @@ describe('ReactPropTypes', function() {
378 typeCheckPass(PropTypes.node, <MyComponent />);
379 typeCheckPass(PropTypes.node, 'Some string');
380 typeCheckPass(PropTypes.node, []);
380 - typeCheckPass(PropTypes.node, {});
381
382 typeCheckPass(PropTypes.node, [
383 123,
@@ -402,20 +402,6 @@ describe('ReactPropTypes', function() {
402 k5: undefined,
403 }));
404 expect(console.error.calls).toEqual([]);
405 -
406 - // This should also pass, though it warns
407 - typeCheckPass(PropTypes.node, {
408 - k0: 123,
409 - k1: 'Some string',
410 - k2: <div />,
411 - k3: {
412 - k30: <MyComponent />,
413 - k31: {k310: <a />},
414 - k32: 'Another string',
415 - },
416 - k4: null,
417 - k5: undefined,
418 - });
405 });
406
407 it('should not warn for iterables', function() {
src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
-17
@@ -15,7 +15,6 @@
15 // of dynamic errors when using JSX with Flow.
16
17 var React;
18 -var ReactFragment;
18 var ReactTestUtils;
19
20 describe('ReactJSXElementValidator', function() {
@@ -26,7 +25,6 @@ describe('ReactJSXElementValidator', function() {
25 require('mock-modules').dumpCache();
26
27 React = require('React');
29 - ReactFragment = require('ReactFragment');
28 ReactTestUtils = require('ReactTestUtils');
29
30 Component = class {
@@ -44,10 +42,6 @@ describe('ReactJSXElementValidator', function() {
42 RequiredPropComponent.propTypes = {prop: React.PropTypes.string.isRequired};
43 });
44
47 - function frag(obj) {
48 - return ReactFragment.create(obj);
49 - }
50 -
45 it('warns for keys for arrays of elements in children position', function() {
46 spyOn(console, 'error');
47
@@ -142,17 +136,6 @@ describe('ReactJSXElementValidator', function() {
136 expect(console.error.argsForCall.length).toBe(0);
137 });
138
145 - it('warns for numeric keys on objects as children', function() {
146 - spyOn(console, 'error');
147 -
148 - void <Component>{frag({1: <Component />, 2: <Component />})}</Component>;
149 -
150 - expect(console.error.argsForCall.length).toBe(1);
151 - expect(console.error.argsForCall[0][0]).toContain(
152 - 'Child objects should have non-numeric keys so ordering is preserved.'
153 - );
154 - });
155 -
139 it('does not warn for numeric keys in entry iterable as a child', function() {
140 spyOn(console, 'error');
141
src/renderers/shared/reconciler/__tests__/ReactIdentity-test.js
+4 -4
@@ -53,8 +53,8 @@ describe('ReactIdentity', function() {
53 instance = ReactDOM.render(instance, document.createElement('div'));
54 var node = ReactDOM.findDOMNode(instance);
55 expect(node.childNodes.length).toBe(2);
56 - checkID(node.childNodes[0], '.0.$first:0');
57 - checkID(node.childNodes[1], '.0.$second:0');
56 + checkID(node.childNodes[0], '.0.$first/=10');
57 + checkID(node.childNodes[1], '.0.$second/=10');
58 });
59
60 it('should allow key property to express identity', function() {
@@ -125,9 +125,9 @@ describe('ReactIdentity', function() {
125 expect(ReactDOM.findDOMNode(span2)).not.toBe(null);
126
127 key = key.replace(/=/g, '=0');
128 -
128 checkID(ReactDOM.findDOMNode(span1), '.0.$' + key);
130 - checkID(ReactDOM.findDOMNode(span2), '.0.1:$' + key + ':0');
129 + key = key.replace(/\//g, '//');
130 + checkID(ReactDOM.findDOMNode(span2), '.0.1:$' + key + '/=10');
131 }
132
133 it('should allow any character as a key, in a detached parent', function() {
src/shared/utils/__tests__/traverseAllChildren-test.js
+10 -19
@@ -162,10 +162,10 @@ describe('traverseAllChildren', function() {
162 traverseContext, div, '.$divNode'
163 );
164 expect(traverseFn).toHaveBeenCalledWith(
165 - traverseContext, span, '.1:0:$span:$spanNode'
165 + traverseContext, <span key="span/.$spanNode" />, '.1:0:$span/=1$spanNode'
166 );
167 expect(traverseFn).toHaveBeenCalledWith(
168 - traverseContext, a, '.2:$a:$aNode'
168 + traverseContext, <a key="a/.$aNode" />, '.2:$a/=1$aNode'
169 );
170 expect(traverseFn).toHaveBeenCalledWith(
171 traverseContext, 'string', '.3'
@@ -224,35 +224,26 @@ describe('traverseAllChildren', function() {
224 expect(traverseContext.length).toEqual(6);
225 expect(traverseFn).toHaveBeenCalledWith(
226 traverseContext,
227 - zero,
228 - '.0:$firstHalfKey:0:$keyZero'
229 - );
230 -
231 - expect(traverseFn)
232 - .toHaveBeenCalledWith(traverseContext, one, '.0:$firstHalfKey:0:1');
233 -
234 - expect(traverseFn).toHaveBeenCalledWith(
235 - traverseContext,
236 - two,
237 - '.0:$firstHalfKey:0:$keyTwo'
227 + <div key="firstHalfKey/.$keyZero" />,
228 + '.0:$firstHalfKey/=1$keyZero'
229 );
230
231 expect(traverseFn).toHaveBeenCalledWith(
232 traverseContext,
242 - three,
243 - '.0:$secondHalfKey:0:0'
233 + <div key="firstHalfKey/.$keyTwo" />,
234 + '.0:$firstHalfKey/=1$keyTwo'
235 );
236
237 expect(traverseFn).toHaveBeenCalledWith(
238 traverseContext,
248 - four,
249 - '.0:$secondHalfKey:0:$keyFour'
239 + <div key="secondHalfKey/.$keyFour" />,
240 + '.0:$secondHalfKey/=1$keyFour'
241 );
242
243 expect(traverseFn).toHaveBeenCalledWith(
244 traverseContext,
254 - five,
255 - '.0:$keyFive:$keyFiveInner'
245 + <div key="keyFive/.$keyFiveInner" />,
246 + '.0:$keyFive/=1$keyFiveInner'
247 );
248 });
249
src/shared/utils/traverseAllChildren.js
+17 -21
@@ -11,8 +11,8 @@
11
12 'use strict';
13
14 +var ReactCurrentOwner = require('ReactCurrentOwner');
15 var ReactElement = require('ReactElement');
15 -var ReactFragment = require('ReactFragment');
16 var ReactInstanceHandles = require('ReactInstanceHandles');
17
18 var getIteratorFn = require('getIteratorFn');
@@ -179,28 +179,24 @@ function traverseAllChildrenImpl(
179 }
180 }
181 } else if (type === 'object') {
182 - invariant(
183 - children.nodeType !== 1,
184 - 'traverseAllChildren(...): Encountered an invalid child; DOM ' +
185 - 'elements are not valid children of React components.'
186 - );
187 - var fragment = ReactFragment.extract(children);
188 - for (var key in fragment) {
189 - if (fragment.hasOwnProperty(key)) {
190 - child = fragment[key];
191 - nextName = (
192 - nextNamePrefix +
193 - wrapUserProvidedKey(key) + SUBSEPARATOR +
194 - getComponentKey(child, 0)
195 - );
196 - subtreeCount += traverseAllChildrenImpl(
197 - child,
198 - nextName,
199 - callback,
200 - traverseContext
201 - );
182 + var addendum = '';
183 + if (__DEV__) {
184 + if (ReactCurrentOwner.current) {
185 + var name = ReactCurrentOwner.current.getName();
186 + if (name) {
187 + addendum = ' Check the render method of `' + name + '`.'
188 + }
189 }
190 }
191 + invariant(
192 + false,
193 + 'Objects are not valid as a React child (found object with keys ' +
194 + '{%s}). If you meant to render a collection of children, use an ' +
195 + 'array instead or wrap the object using ' +
196 + 'React.addons.createFragment(object).%s',
197 + Object.keys(children).join(', '),
198 + addendum
199 + );
200 }
201 }
202