@samitouri / QOS-React / commits / c6bde7b99f

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', () => {