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