support Call and Return components in React.Children calls (#11422)
* support Call and Return components in React.Children calls * make tests more verbose * fix ordering of React component types * cleanup conditional detection of children type * directly inline callback invocation * reduce callback invocation code re-use
Matteo Vesprini-Heidrich committed
Nov 20, 2017 at 17:06 UTC
c6bde7b99f66fa214b5ce4b4f70751a6ab591449
2 files changed
+84
-16
packages/react/src/ReactChildren.js
+28
-9
@@ -19,6 +19,12 @@ var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
19
var REACT_ELEMENT_TYPE =
20
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
21
0xeac7;
22
+const REACT_CALL_TYPE =
23
+ (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.call')) ||
24
+ 0xeac8;
25
+const REACT_RETURN_TYPE =
26
+ (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.return')) ||
27
+ 0xeac9;
28
const REACT_PORTAL_TYPE =
29
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.portal')) ||
30
0xeaca;
@@ -115,15 +121,28 @@ function traverseAllChildrenImpl(
121
children = null;
122
}
123
118
- if (
119
- children === null ||
120
- type === 'string' ||
121
- type === 'number' ||
122
- // The following is inlined from ReactElement. This means we can optimize
123
- // some checks. React Fiber also inlines this logic for similar purposes.
124
- (type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE) ||
125
- (type === 'object' && children.$$typeof === REACT_PORTAL_TYPE)
126
- ) {
124
+ let invokeCallback = false;
125
+
126
+ if (children === null) {
127
+ invokeCallback = true;
128
+ } else {
129
+ switch (type) {
130
+ case 'string':
131
+ case 'number':
132
+ invokeCallback = true;
133
+ break;
134
+ case 'object':
135
+ switch (children.$$typeof) {
136
+ case REACT_ELEMENT_TYPE:
137
+ case REACT_CALL_TYPE:
138
+ case REACT_RETURN_TYPE:
139
+ case REACT_PORTAL_TYPE:
140
+ invokeCallback = true;
141
+ }
142
+ }
143
+ }
144
+
145
+ if (invokeCallback) {
146
callback(
147
traverseContext,
148
children,
packages/react/src/__tests__/ReactChildren-test.js
+56
-7
@@ -58,19 +58,68 @@ describe('ReactChildren', () => {
58
const portalContainer = document.createElement('div');
59
60
const simpleChild = <span key="simple" />;
61
- const portal = ReactDOM.createPortal(simpleChild, portalContainer);
62
- const instance = <div>{portal}</div>;
61
+ const reactPortal = ReactDOM.createPortal(simpleChild, portalContainer);
62
64
- React.Children.forEach(instance.props.children, callback, context);
65
- expect(callback).toHaveBeenCalledWith(portal, 0);
63
+ const parentInstance = <div>{reactPortal}</div>;
64
+ React.Children.forEach(parentInstance.props.children, callback, context);
65
+ expect(callback).toHaveBeenCalledWith(reactPortal, 0);
66
callback.calls.reset();
67
const mappedChildren = React.Children.map(
68
- instance.props.children,
68
+ parentInstance.props.children,
69
+ callback,
70
+ context,
71
+ );
72
+ expect(callback).toHaveBeenCalledWith(reactPortal, 0);
73
+ expect(mappedChildren[0]).toEqual(reactPortal);
74
+ });
75
+
76
+ it('should support Call components', () => {
77
+ const context = {};
78
+ const callback = jasmine.createSpy().and.callFake(function(kid, index) {
79
+ expect(this).toBe(context);
80
+ return kid;
81
+ });
82
+ const ReactCallReturn = require('react-call-return');
83
+ const reactCall = ReactCallReturn.unstable_createCall(
84
+ <span key="simple" />,
85
+ () => {},
86
+ );
87
+
88
+ const parentInstance = <div>{reactCall}</div>;
89
+ React.Children.forEach(parentInstance.props.children, callback, context);
90
+ expect(callback).toHaveBeenCalledWith(reactCall, 0);
91
+ callback.calls.reset();
92
+ const mappedChildren = React.Children.map(
93
+ parentInstance.props.children,
94
+ callback,
95
+ context,
96
+ );
97
+ expect(callback).toHaveBeenCalledWith(reactCall, 0);
98
+ expect(mappedChildren[0]).toEqual(reactCall);
99
+ });
100
+
101
+ it('should support Return components', () => {
102
+ const context = {};
103
+ const callback = jasmine.createSpy().and.callFake(function(kid, index) {
104
+ expect(this).toBe(context);
105
+ return kid;
106
+ });
107
+ const ReactCallReturn = require('react-call-return');
108
+ const reactReturn = ReactCallReturn.unstable_createReturn(
109
+ <span key="simple" />,
110
+ );
111
+
112
+ const parentInstance = <div>{reactReturn}</div>;
113
+ React.Children.forEach(parentInstance.props.children, callback, context);
114
+ expect(callback).toHaveBeenCalledWith(reactReturn, 0);
115
+ callback.calls.reset();
116
+ const mappedChildren = React.Children.map(
117
+ parentInstance.props.children,
118
callback,
119
context,
120
);
72
- expect(callback).toHaveBeenCalledWith(portal, 0);
73
- expect(mappedChildren[0]).toEqual(portal);
121
+ expect(callback).toHaveBeenCalledWith(reactReturn, 0);
122
+ expect(mappedChildren[0]).toEqual(reactReturn);
123
});
124
125
it('should treat single arrayless child as being in array', () => {