Skip to content

Commit 742654c

Browse files
Matteo Vesprini-Heidrichraphamorim
authored andcommitted
support Call and Return components in React.Children calls (facebook#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
1 parent b764450 commit 742654c

File tree

2 files changed

+84
-16
lines changed

2 files changed

+84
-16
lines changed

packages/react/src/ReactChildren.js

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
1919
var REACT_ELEMENT_TYPE =
2020
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
2121
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;
2228
const REACT_PORTAL_TYPE =
2329
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.portal')) ||
2430
0xeaca;
@@ -115,15 +121,28 @@ function traverseAllChildrenImpl(
115121
children = null;
116122
}
117123

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) {
127146
callback(
128147
traverseContext,
129148
children,

packages/react/src/__tests__/ReactChildren-test.js

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,68 @@ describe('ReactChildren', () => {
5858
const portalContainer = document.createElement('div');
5959

6060
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);
6362

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);
6666
callback.calls.reset();
6767
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,
69118
callback,
70119
context,
71120
);
72-
expect(callback).toHaveBeenCalledWith(portal, 0);
73-
expect(mappedChildren[0]).toEqual(portal);
121+
expect(callback).toHaveBeenCalledWith(reactReturn, 0);
122+
expect(mappedChildren[0]).toEqual(reactReturn);
74123
});
75124

76125
it('should treat single arrayless child as being in array', () => {

0 commit comments

Comments
 (0)