Add React.Children.toArray
Ben Alpert committed
Apr 11, 2015 at 00:55 UTC
53d8bbddc7322f165b966f862043881ac31583a9
4 files changed
+85
src/isomorphic/ReactIsomorphic.js
+1
@@ -40,6 +40,7 @@ var React = {
40
map: ReactChildren.map,
41
forEach: ReactChildren.forEach,
42
count: ReactChildren.count,
43
+ toArray: ReactChildren.toArray,
44
only: onlyChild,
45
},
46
src/isomorphic/children/ReactChildren.js
+32
@@ -12,6 +12,7 @@
12
'use strict';
13
14
var PooledClass = require('PooledClass');
15
+var ReactElement = require('ReactElement');
16
var ReactFragment = require('ReactFragment');
17
18
var traverseAllChildren = require('traverseAllChildren');
@@ -20,6 +21,7 @@ var warning = require('warning');
21
var twoArgumentPooler = PooledClass.twoArgumentPooler;
22
var threeArgumentPooler = PooledClass.threeArgumentPooler;
23
24
+
25
/**
26
* PooledClass representing the bookkeeping associated with performing a child
27
* traversal. Allows avoiding binding callbacks.
@@ -61,6 +63,7 @@ function forEachChildren(children, forEachFunc, forEachContext) {
63
ForEachBookKeeping.release(traverseContext);
64
}
65
66
+
67
/**
68
* PooledClass representing the bookkeeping associated with performing a child
69
* mapping. Allows avoiding binding callbacks.
@@ -126,6 +129,7 @@ function mapChildren(children, func, context) {
129
return ReactFragment.create(mapResult);
130
}
131
132
+
133
function forEachSingleChildDummy(traverseContext, child, name) {
134
return null;
135
}
@@ -141,10 +145,38 @@ function countChildren(children, context) {
145
return traverseAllChildren(children, forEachSingleChildDummy, null);
146
}
147
148
+
149
+function pushSingleChildToArray(traverseContext, child, name, i) {
150
+ if (child == null) {
151
+ return;
152
+ }
153
+
154
+ var result = traverseContext;
155
+
156
+ if (ReactElement.isValidElement(child)) {
157
+ child = ReactElement.cloneAndReplaceKey(child, name);
158
+ }
159
+ // For text components, leave unkeyed
160
+
161
+ result.push(child);
162
+}
163
+
164
+/**
165
+ * Flatten a children object (typically specified as `props.children`) and
166
+ * return an array with appropriately re-keyed children.
167
+ */
168
+function toArray(children) {
169
+ var result = [];
170
+ traverseAllChildren(children, pushSingleChildToArray, result);
171
+ return result;
172
+}
173
+
174
+
175
var ReactChildren = {
176
forEach: forEachChildren,
177
map: mapChildren,
178
count: countChildren,
179
+ toArray: toArray,
180
};
181
182
module.exports = ReactChildren;
src/isomorphic/children/__tests__/ReactChildren-test.js
+38
@@ -458,4 +458,42 @@ describe('ReactChildren', function() {
458
expect(console.error.calls[1].args[0]).toContain('is an immutable opaque');
459
});
460
461
+ it('should flatten children to an array', function() {
462
+ expect(ReactChildren.toArray(undefined)).toEqual([]);
463
+ expect(ReactChildren.toArray(null)).toEqual([]);
464
+
465
+ expect(ReactChildren.toArray(<div />).length).toBe(1);
466
+ expect(ReactChildren.toArray([<div />]).length).toBe(1);
467
+ expect(
468
+ ReactChildren.toArray(<div />)[0].key
469
+ ).toBe(
470
+ ReactChildren.toArray([<div />])[0].key
471
+ );
472
+
473
+ var flattened = ReactChildren.toArray([
474
+ [<div key="apple" />, <div key="banana" />, <div key="camel" />],
475
+ [<div key="banana" />, <div key="camel" />, <div key="deli" />],
476
+ ]);
477
+ expect(flattened.length).toBe(6);
478
+ expect(flattened[1].key).toContain('banana');
479
+ expect(flattened[3].key).toContain('banana');
480
+ expect(flattened[1].key).not.toBe(flattened[3].key);
481
+
482
+ var reversed = ReactChildren.toArray([
483
+ [<div key="camel" />, <div key="banana" />, <div key="apple" />],
484
+ [<div key="deli" />, <div key="camel" />, <div key="banana" />],
485
+ ]);
486
+ expect(flattened[0].key).toBe(reversed[2].key);
487
+ expect(flattened[1].key).toBe(reversed[1].key);
488
+ expect(flattened[2].key).toBe(reversed[0].key);
489
+ expect(flattened[3].key).toBe(reversed[5].key);
490
+ expect(flattened[4].key).toBe(reversed[4].key);
491
+ expect(flattened[5].key).toBe(reversed[3].key);
492
+
493
+ // null/undefined/bool are all omitted
494
+ expect(ReactChildren.toArray([1, 'two', null, undefined, true])).toEqual(
495
+ [1, 'two']
496
+ );
497
+ });
498
+
499
});
src/isomorphic/classic/element/ReactElement.js
+14
@@ -155,6 +155,20 @@ ReactElement.createFactory = function(type) {
155
return factory;
156
};
157
158
+ReactElement.cloneAndReplaceKey = function(oldElement, newKey) {
159
+ var newElement = new ReactElement(
160
+ oldElement.type,
161
+ newKey,
162
+ oldElement.ref,
163
+ oldElement.self,
164
+ oldElement.source,
165
+ oldElement._owner,
166
+ oldElement.props
167
+ );
168
+
169
+ return newElement;
170
+};
171
+
172
ReactElement.cloneAndReplaceProps = function(oldElement, newProps) {
173
var newElement = new ReactElement(
174
oldElement.type,