Skip to content

Commit

Permalink
support Call and Return components in React.Children calls (#11422)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Matteo Vesprini-Heidrich authored and gaearon committed Nov 20, 2017
1 parent dbf715c commit c6bde7b
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 16 deletions.
37 changes: 28 additions & 9 deletions packages/react/src/ReactChildren.js
Expand Up @@ -19,6 +19,12 @@ var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
var REACT_ELEMENT_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
0xeac7;
const REACT_CALL_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.call')) ||
0xeac8;
const REACT_RETURN_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.return')) ||
0xeac9;
const REACT_PORTAL_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.portal')) ||
0xeaca;
Expand Down Expand Up @@ -115,15 +121,28 @@ function traverseAllChildrenImpl(
children = null;
}

if (
children === null ||
type === 'string' ||
type === 'number' ||
// The following is inlined from ReactElement. This means we can optimize
// some checks. React Fiber also inlines this logic for similar purposes.
(type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE) ||
(type === 'object' && children.$$typeof === REACT_PORTAL_TYPE)
) {
let invokeCallback = false;

if (children === null) {
invokeCallback = true;
} else {
switch (type) {
case 'string':
case 'number':
invokeCallback = true;
break;
case 'object':
switch (children.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_CALL_TYPE:
case REACT_RETURN_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
}
}
}

if (invokeCallback) {
callback(
traverseContext,
children,
Expand Down
63 changes: 56 additions & 7 deletions packages/react/src/__tests__/ReactChildren-test.js
Expand Up @@ -58,19 +58,68 @@ describe('ReactChildren', () => {
const portalContainer = document.createElement('div');

const simpleChild = <span key="simple" />;
const portal = ReactDOM.createPortal(simpleChild, portalContainer);
const instance = <div>{portal}</div>;
const reactPortal = ReactDOM.createPortal(simpleChild, portalContainer);

React.Children.forEach(instance.props.children, callback, context);
expect(callback).toHaveBeenCalledWith(portal, 0);
const parentInstance = <div>{reactPortal}</div>;
React.Children.forEach(parentInstance.props.children, callback, context);
expect(callback).toHaveBeenCalledWith(reactPortal, 0);
callback.calls.reset();
const mappedChildren = React.Children.map(
instance.props.children,
parentInstance.props.children,
callback,
context,
);
expect(callback).toHaveBeenCalledWith(reactPortal, 0);
expect(mappedChildren[0]).toEqual(reactPortal);
});

it('should support Call components', () => {
const context = {};
const callback = jasmine.createSpy().and.callFake(function(kid, index) {
expect(this).toBe(context);
return kid;
});
const ReactCallReturn = require('react-call-return');
const reactCall = ReactCallReturn.unstable_createCall(
<span key="simple" />,
() => {},
);

const parentInstance = <div>{reactCall}</div>;
React.Children.forEach(parentInstance.props.children, callback, context);
expect(callback).toHaveBeenCalledWith(reactCall, 0);
callback.calls.reset();
const mappedChildren = React.Children.map(
parentInstance.props.children,
callback,
context,
);
expect(callback).toHaveBeenCalledWith(reactCall, 0);
expect(mappedChildren[0]).toEqual(reactCall);
});

it('should support Return components', () => {
const context = {};
const callback = jasmine.createSpy().and.callFake(function(kid, index) {
expect(this).toBe(context);
return kid;
});
const ReactCallReturn = require('react-call-return');
const reactReturn = ReactCallReturn.unstable_createReturn(
<span key="simple" />,
);

const parentInstance = <div>{reactReturn}</div>;
React.Children.forEach(parentInstance.props.children, callback, context);
expect(callback).toHaveBeenCalledWith(reactReturn, 0);
callback.calls.reset();
const mappedChildren = React.Children.map(
parentInstance.props.children,
callback,
context,
);
expect(callback).toHaveBeenCalledWith(portal, 0);
expect(mappedChildren[0]).toEqual(portal);
expect(callback).toHaveBeenCalledWith(reactReturn, 0);
expect(mappedChildren[0]).toEqual(reactReturn);
});

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

0 comments on commit c6bde7b

Please sign in to comment.