@samitouri / QOS-React / commits / 6a1e6b2f78

Experimental event API: loosen EventTarget constraints and warnings (#15292)

* Remove warning for event targets being direct children of event component * Addressed feedback and added more test coverage + warnings

Dominic Gannaway committed Apr 2, 2019 at 19:49 UTC 6a1e6b2f78da3a56aa497902951c6e9ce654eafc
14 files changed +577 -309
packages/react-art/src/ReactARTHostConfig.js
+6 -1
@@ -340,7 +340,11 @@ export function getChildHostContext() {
340 return NO_CONTEXT;
341 }
342
343 -export function getChildHostContextForEvent() {
343 +export function getChildHostContextForEventComponent() {
344 + return NO_CONTEXT;
345 +}
346 +
347 +export function getChildHostContextForEventTarget() {
348 return NO_CONTEXT;
349 }
350
@@ -446,6 +450,7 @@ export function handleEventComponent(
450 export function handleEventTarget(
451 type: Symbol | number,
452 props: Props,
453 + parentInstance: Container,
454 internalInstanceHandle: Object,
455 ) {
456 // TODO: add handleEventTarget implementation
packages/react-dom/src/client/ReactDOMHostConfig.js
+56 -45
@@ -45,12 +45,7 @@ import dangerousStyleValue from '../shared/dangerousStyleValue';
45
46 import type {DOMContainer} from './ReactDOM';
47 import type {ReactEventResponder} from 'shared/ReactTypes';
48 -import {
49 - REACT_EVENT_COMPONENT_TYPE,
50 - REACT_EVENT_TARGET_TYPE,
51 - REACT_EVENT_TARGET_TOUCH_HIT,
52 -} from 'shared/ReactSymbols';
53 -import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget';
48 +import {REACT_EVENT_TARGET_TOUCH_HIT} from 'shared/ReactSymbols';
49
50 export type Type = string;
51 export type Props = {
@@ -75,6 +70,7 @@ type HostContextDev = {
70 eventData: null | {|
71 isEventComponent?: boolean,
72 isEventTarget?: boolean,
73 + eventTargetType?: null | Symbol | number,
74 |},
75 };
76 type HostContextProd = string;
@@ -180,36 +176,46 @@ export function getChildHostContext(
176 return getChildNamespace(parentNamespace, type);
177 }
178
183 -export function getChildHostContextForEvent(
179 +export function getChildHostContextForEventComponent(
180 parentHostContext: HostContext,
185 - type: Symbol | number,
181 ): HostContext {
182 if (__DEV__) {
183 const parentHostContextDev = ((parentHostContext: any): HostContextDev);
184 const {namespace, ancestorInfo} = parentHostContextDev;
190 - let eventData = null;
185 + warning(
186 + parentHostContextDev.eventData === null ||
187 + !parentHostContextDev.eventData.isEventTarget,
188 + 'validateDOMNesting: React event targets must not have event components as children.',
189 + );
190 + const eventData = {
191 + isEventComponent: true,
192 + isEventTarget: false,
193 + eventTargetType: null,
194 + };
195 + return {namespace, ancestorInfo, eventData};
196 + }
197 + return parentHostContext;
198 +}
199
192 - if (type === REACT_EVENT_COMPONENT_TYPE) {
193 - warning(
194 - parentHostContextDev.eventData === null ||
195 - !parentHostContextDev.eventData.isEventTarget,
196 - 'validateDOMNesting: React event targets must not have event components as children.',
197 - );
198 - eventData = {
199 - isEventComponent: true,
200 - isEventTarget: false,
201 - };
202 - } else if (type === REACT_EVENT_TARGET_TYPE) {
203 - warning(
204 - parentHostContextDev.eventData !== null &&
205 - parentHostContextDev.eventData.isEventComponent,
206 - 'validateDOMNesting: React event targets must be direct children of event components.',
207 - );
208 - eventData = {
209 - isEventComponent: false,
210 - isEventTarget: true,
211 - };
212 - }
200 +export function getChildHostContextForEventTarget(
201 + parentHostContext: HostContext,
202 + type: Symbol | number,
203 +): HostContext {
204 + if (__DEV__) {
205 + const parentHostContextDev = ((parentHostContext: any): HostContextDev);
206 + const {namespace, ancestorInfo} = parentHostContextDev;
207 + warning(
208 + parentHostContextDev.eventData === null ||
209 + !parentHostContextDev.eventData.isEventComponent ||
210 + type !== REACT_EVENT_TARGET_TOUCH_HIT,
211 + 'validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
212 + 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
213 + );
214 + const eventData = {
215 + isEventComponent: false,
216 + isEventTarget: true,
217 + eventTargetType: type,
218 + };
219 return {namespace, ancestorInfo, eventData};
220 }
221 return parentHostContext;
@@ -243,6 +249,16 @@ export function createInstance(
249 if (__DEV__) {
250 // TODO: take namespace into account when validating.
251 const hostContextDev = ((hostContext: any): HostContextDev);
252 + if (enableEventAPI) {
253 + const eventData = hostContextDev.eventData;
254 + if (eventData !== null) {
255 + warning(
256 + !eventData.isEventTarget ||
257 + eventData.eventTargetType !== REACT_EVENT_TARGET_TOUCH_HIT,
258 + 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
259 + );
260 + }
261 + }
262 validateDOMNesting(type, null, hostContextDev.ancestorInfo);
263 if (
264 typeof props.children === 'string' ||
@@ -349,6 +365,12 @@ export function createTextInstance(
365 if (enableEventAPI) {
366 const eventData = hostContextDev.eventData;
367 if (eventData !== null) {
368 + warning(
369 + eventData === null ||
370 + !eventData.isEventTarget ||
371 + eventData.eventTargetType !== REACT_EVENT_TARGET_TOUCH_HIT,
372 + 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
373 + );
374 warning(
375 !eventData.isEventComponent,
376 'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
@@ -356,7 +378,8 @@ export function createTextInstance(
378 text,
379 );
380 warning(
359 - !eventData.isEventTarget,
381 + !eventData.isEventTarget ||
382 + eventData.eventTargetType === REACT_EVENT_TARGET_TOUCH_HIT,
383 'validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
384 'Wrap the child text "%s" in an element.',
385 text,
@@ -879,25 +902,13 @@ export function handleEventComponent(
902 export function handleEventTarget(
903 type: Symbol | number,
904 props: Props,
905 + parentInstance: Container,
906 internalInstanceHandle: Object,
907 ): void {
908 if (enableEventAPI) {
909 // Touch target hit slop handling
910 if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
887 - // Validates that there is a single element
888 - const element = getElementFromTouchHitTarget(internalInstanceHandle);
889 - if (element !== null) {
890 - // We update the event target state node to be that of the element.
891 - // We can then diff this entry to determine if we need to add the
892 - // hit slop element, or change the dimensions of the hit slop.
893 - const lastElement = internalInstanceHandle.stateNode;
894 - if (lastElement !== element) {
895 - internalInstanceHandle.stateNode = element;
896 - // TODO: Create the hit slop element and attach it to the element
897 - } else {
898 - // TODO: Diff the left, top, right, bottom props
899 - }
900 - }
911 + // TODO
912 }
913 }
914 }
packages/react-events/src/__tests__/TouchHitTarget-test.internal.js new
+322
@@ -0,0 +1,322 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +let React;
13 +let ReactNoop;
14 +let Scheduler;
15 +let ReactFeatureFlags;
16 +let EventComponent;
17 +let ReactTestRenderer;
18 +let ReactDOM;
19 +let ReactSymbols;
20 +let ReactEvents;
21 +let TouchHitTarget;
22 +
23 +const noOpResponder = {
24 + targetEventTypes: [],
25 + handleEvent() {},
26 +};
27 +
28 +function createReactEventComponent() {
29 + return {
30 + $$typeof: ReactSymbols.REACT_EVENT_COMPONENT_TYPE,
31 + props: null,
32 + responder: noOpResponder,
33 + };
34 +}
35 +
36 +function init() {
37 + jest.resetModules();
38 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
39 + ReactFeatureFlags.enableEventAPI = true;
40 + React = require('react');
41 + Scheduler = require('scheduler');
42 + ReactSymbols = require('shared/ReactSymbols');
43 + ReactEvents = require('react-events');
44 +}
45 +
46 +function initNoopRenderer() {
47 + init();
48 + ReactNoop = require('react-noop-renderer');
49 +}
50 +
51 +function initTestRenderer() {
52 + init();
53 + ReactTestRenderer = require('react-test-renderer');
54 +}
55 +
56 +function initReactDOM() {
57 + init();
58 + ReactDOM = require('react-dom');
59 +}
60 +
61 +describe('TouchHitTarget', () => {
62 + describe('NoopRenderer', () => {
63 + beforeEach(() => {
64 + initNoopRenderer();
65 + EventComponent = createReactEventComponent();
66 + TouchHitTarget = ReactEvents.TouchHitTarget;
67 + });
68 +
69 + it('should not warn when a TouchHitTarget is used correctly', () => {
70 + const Test = () => (
71 + <EventComponent>
72 + <div>
73 + <TouchHitTarget />
74 + </div>
75 + </EventComponent>
76 + );
77 +
78 + ReactNoop.render(<Test />);
79 + expect(Scheduler).toFlushWithoutYielding();
80 + expect(ReactNoop).toMatchRenderedOutput(<div />);
81 + });
82 +
83 + it('should warn when a TouchHitTarget has children', () => {
84 + const Test = () => (
85 + <EventComponent>
86 + <div>
87 + <TouchHitTarget>
88 + <span>Child 1</span>
89 + </TouchHitTarget>
90 + </div>
91 + </EventComponent>
92 + );
93 +
94 + expect(() => {
95 + ReactNoop.render(<Test />);
96 + expect(Scheduler).toFlushWithoutYielding();
97 + }).toWarnDev(
98 + 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
99 + );
100 +
101 + const Test2 = () => (
102 + <EventComponent>
103 + <div>
104 + <TouchHitTarget>Child 1</TouchHitTarget>
105 + </div>
106 + </EventComponent>
107 + );
108 +
109 + expect(() => {
110 + ReactNoop.render(<Test2 />);
111 + expect(Scheduler).toFlushWithoutYielding();
112 + }).toWarnDev(
113 + 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
114 + );
115 +
116 + // Should render without warnings
117 + const Test3 = () => (
118 + <EventComponent>
119 + <div>
120 + <TouchHitTarget />
121 + </div>
122 + </EventComponent>
123 + );
124 +
125 + ReactNoop.render(<Test3 />);
126 + expect(Scheduler).toFlushWithoutYielding();
127 + expect(ReactNoop).toMatchRenderedOutput(<div />);
128 + });
129 +
130 + it('should warn when a TouchHitTarget is a direct child of an event component', () => {
131 + const Test = () => (
132 + <EventComponent>
133 + <TouchHitTarget />
134 + </EventComponent>
135 + );
136 +
137 + expect(() => {
138 + ReactNoop.render(<Test />);
139 + expect(Scheduler).toFlushWithoutYielding();
140 + }).toWarnDev(
141 + 'Warning: validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
142 + 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
143 + );
144 + });
145 + });
146 +
147 + describe('TestRenderer', () => {
148 + beforeEach(() => {
149 + initTestRenderer();
150 + EventComponent = createReactEventComponent();
151 + TouchHitTarget = ReactEvents.TouchHitTarget;
152 + });
153 +
154 + it('should not warn when a TouchHitTarget is used correctly', () => {
155 + const Test = () => (
156 + <EventComponent>
157 + <div>
158 + <TouchHitTarget />
159 + </div>
160 + </EventComponent>
161 + );
162 +
163 + const root = ReactTestRenderer.create(null);
164 + root.update(<Test />);
165 + expect(Scheduler).toFlushWithoutYielding();
166 + expect(root).toMatchRenderedOutput(<div />);
167 + });
168 +
169 + it('should warn when a TouchHitTarget has children', () => {
170 + const Test = () => (
171 + <EventComponent>
172 + <div>
173 + <TouchHitTarget>
174 + <span>Child 1</span>
175 + </TouchHitTarget>
176 + </div>
177 + </EventComponent>
178 + );
179 +
180 + const root = ReactTestRenderer.create(null);
181 + expect(() => {
182 + root.update(<Test />);
183 + expect(Scheduler).toFlushWithoutYielding();
184 + }).toWarnDev(
185 + 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
186 + );
187 +
188 + const Test2 = () => (
189 + <EventComponent>
190 + <div>
191 + <TouchHitTarget>Child 1</TouchHitTarget>
192 + </div>
193 + </EventComponent>
194 + );
195 +
196 + expect(() => {
197 + root.update(<Test2 />);
198 + expect(Scheduler).toFlushWithoutYielding();
199 + }).toWarnDev(
200 + 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
201 + );
202 +
203 + // Should render without warnings
204 + const Test3 = () => (
205 + <EventComponent>
206 + <div>
207 + <TouchHitTarget />
208 + </div>
209 + </EventComponent>
210 + );
211 +
212 + root.update(<Test3 />);
213 + expect(Scheduler).toFlushWithoutYielding();
214 + expect(root).toMatchRenderedOutput(<div />);
215 + });
216 +
217 + it('should warn when a TouchHitTarget is a direct child of an event component', () => {
218 + const Test = () => (
219 + <EventComponent>
220 + <TouchHitTarget />
221 + </EventComponent>
222 + );
223 +
224 + const root = ReactTestRenderer.create(null);
225 + expect(() => {
226 + root.update(<Test />);
227 + expect(Scheduler).toFlushWithoutYielding();
228 + }).toWarnDev(
229 + 'Warning: validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
230 + 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
231 + );
232 + });
233 + });
234 +
235 + describe('ReactDOM', () => {
236 + beforeEach(() => {
237 + initReactDOM();
238 + EventComponent = createReactEventComponent();
239 + TouchHitTarget = ReactEvents.TouchHitTarget;
240 + });
241 +
242 + it('should not warn when a TouchHitTarget is used correctly', () => {
243 + const Test = () => (
244 + <EventComponent>
245 + <div>
246 + <TouchHitTarget />
247 + </div>
248 + </EventComponent>
249 + );
250 +
251 + const container = document.createElement('div');
252 + ReactDOM.render(<Test />, container);
253 + expect(Scheduler).toFlushWithoutYielding();
254 + expect(container.innerHTML).toBe('<div></div>');
255 + });
256 +
257 + it('should warn when a TouchHitTarget has children', () => {
258 + const Test = () => (
259 + <EventComponent>
260 + <div>
261 + <TouchHitTarget>
262 + <span>Child 1</span>
263 + </TouchHitTarget>
264 + </div>
265 + </EventComponent>
266 + );
267 +
268 + const container = document.createElement('div');
269 + expect(() => {
270 + ReactDOM.render(<Test />, container);
271 + expect(Scheduler).toFlushWithoutYielding();
272 + }).toWarnDev(
273 + 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
274 + );
275 +
276 + const Test2 = () => (
277 + <EventComponent>
278 + <div>
279 + <TouchHitTarget>Child 1</TouchHitTarget>
280 + </div>
281 + </EventComponent>
282 + );
283 +
284 + expect(() => {
285 + ReactDOM.render(<Test2 />, container);
286 + expect(Scheduler).toFlushWithoutYielding();
287 + }).toWarnDev(
288 + 'Warning: validateDOMNesting: <TouchHitTarget> must not have any children.',
289 + );
290 +
291 + // Should render without warnings
292 + const Test3 = () => (
293 + <EventComponent>
294 + <div>
295 + <TouchHitTarget />
296 + </div>
297 + </EventComponent>
298 + );
299 +
300 + ReactDOM.render(<Test3 />, container);
301 + expect(Scheduler).toFlushWithoutYielding();
302 + expect(container.innerHTML).toBe('<div></div>');
303 + });
304 +
305 + it('should warn when a TouchHitTarget is a direct child of an event component', () => {
306 + const Test = () => (
307 + <EventComponent>
308 + <TouchHitTarget />
309 + </EventComponent>
310 + );
311 +
312 + const container = document.createElement('div');
313 + expect(() => {
314 + ReactDOM.render(<Test />, container);
315 + expect(Scheduler).toFlushWithoutYielding();
316 + }).toWarnDev(
317 + 'Warning: validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
318 + 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
319 + );
320 + });
321 + });
322 +});
packages/react-native-renderer/src/ReactFabricHostConfig.js
+10 -2
@@ -283,11 +283,18 @@ export function getChildHostContext(
283 }
284 }
285
286 -export function getChildHostContextForEvent(
286 +export function getChildHostContextForEventComponent(
287 + parentHostContext: HostContext,
288 +) {
289 + // TODO: add getChildHostContextForEventComponent implementation
290 + return parentHostContext;
291 +}
292 +
293 +export function getChildHostContextForEventTarget(
294 parentHostContext: HostContext,
295 type: Symbol | number,
296 ) {
290 - // TODO: add getChildHostContextForEvent implementation
297 + // TODO: add getChildHostContextForEventTarget implementation
298 return parentHostContext;
299 }
300
@@ -438,6 +445,7 @@ export function handleEventComponent(
445 export function handleEventTarget(
446 type: Symbol | number,
447 props: Props,
448 + parentInstance: Container,
449 internalInstanceHandle: Object,
450 ) {
451 // TODO: add handleEventTarget implementation
packages/react-native-renderer/src/ReactNativeHostConfig.js
+10 -2
@@ -206,11 +206,18 @@ export function getChildHostContext(
206 }
207 }
208
209 -export function getChildHostContextForEvent(
209 +export function getChildHostContextForEventComponent(
210 + parentHostContext: HostContext,
211 +) {
212 + // TODO: add getChildHostContextForEventComponent implementation
213 + return parentHostContext;
214 +}
215 +
216 +export function getChildHostContextForEventTarget(
217 parentHostContext: HostContext,
218 type: Symbol | number,
219 ) {
213 - // TODO: add getChildHostContextForEvent implementation
220 + // TODO: add getChildHostContextForEventTarget implementation
221 return parentHostContext;
222 }
223
@@ -497,6 +504,7 @@ export function handleEventComponent(
504 export function handleEventTarget(
505 type: Symbol | number,
506 props: Props,
507 + parentInstance: Container,
508 internalInstanceHandle: Object,
509 ) {
510 // TODO: add handleEventTarget implementation
packages/react-noop-renderer/src/createReactNoop.js
+32 -16
@@ -24,13 +24,10 @@ import expect from 'expect';
24 import {
25 REACT_FRAGMENT_TYPE,
26 REACT_ELEMENT_TYPE,
27 - REACT_EVENT_COMPONENT_TYPE,
28 - REACT_EVENT_TARGET_TYPE,
27 REACT_EVENT_TARGET_TOUCH_HIT,
28 } from 'shared/ReactSymbols';
29 import warningWithoutStack from 'shared/warningWithoutStack';
30 import warning from 'shared/warning';
33 -import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget';
31
32 import {enableEventAPI} from 'shared/ReactFeatureFlags';
33
@@ -66,6 +63,7 @@ const NO_CONTEXT = {};
63 const UPPERCASE_CONTEXT = {};
64 const EVENT_COMPONENT_CONTEXT = {};
65 const EVENT_TARGET_CONTEXT = {};
66 +const EVENT_TOUCH_HIT_TARGET_CONTEXT = {};
67 const UPDATE_SIGNAL = {};
68 if (__DEV__) {
69 Object.freeze(NO_CONTEXT);
@@ -262,24 +260,32 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
260 return NO_CONTEXT;
261 },
262
265 - getChildHostContextForEvent(
263 + getChildHostContextForEventComponent(parentHostContext: HostContext) {
264 + if (__DEV__ && enableEventAPI) {
265 + warning(
266 + parentHostContext !== EVENT_TARGET_CONTEXT &&
267 + parentHostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
268 + 'validateDOMNesting: React event targets must not have event components as children.',
269 + );
270 + return EVENT_COMPONENT_CONTEXT;
271 + }
272 + return parentHostContext;
273 + },
274 +
275 + getChildHostContextForEventTarget(
276 parentHostContext: HostContext,
277 type: Symbol | number,
278 ) {
279 if (__DEV__ && enableEventAPI) {
270 - if (type === REACT_EVENT_COMPONENT_TYPE) {
280 + if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
281 warning(
272 - parentHostContext !== EVENT_TARGET_CONTEXT,
273 - 'validateDOMNesting: React event targets must not have event components as children.',
282 + parentHostContext !== EVENT_COMPONENT_CONTEXT,
283 + 'validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
284 + 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
285 );
275 - return EVENT_COMPONENT_CONTEXT;
276 - } else if (type === REACT_EVENT_TARGET_TYPE) {
277 - warning(
278 - parentHostContext === EVENT_COMPONENT_CONTEXT,
279 - 'validateDOMNesting: React event targets must be direct children of event components.',
280 - );
281 - return EVENT_TARGET_CONTEXT;
286 + return EVENT_TOUCH_HIT_TARGET_CONTEXT;
287 }
288 + return EVENT_TARGET_CONTEXT;
289 }
290 return parentHostContext;
291 },
@@ -294,6 +300,12 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
300 rootContainerInstance: Container,
301 hostContext: HostContext,
302 ): Instance {
303 + if (__DEV__ && enableEventAPI) {
304 + warning(
305 + hostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
306 + 'validateDOMNesting: <TouchHitTarget> must not have any children.',
307 + );
308 + }
309 if (type === 'errorInCompletePhase') {
310 throw new Error('Error in host config.');
311 }
@@ -368,6 +380,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
380 internalInstanceHandle: Object,
381 ): TextInstance {
382 if (__DEV__ && enableEventAPI) {
383 + warning(
384 + hostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
385 + 'validateDOMNesting: <TouchHitTarget> must not have any children.',
386 + );
387 warning(
388 hostContext !== EVENT_COMPONENT_CONTEXT,
389 'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
@@ -419,11 +435,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
435 handleEventTarget(
436 type: Symbol | number,
437 props: Props,
438 + parentInstance: Container,
439 internalInstanceHandle: Object,
440 ) {
441 if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
425 - // Validates that there is a single element
426 - getElementFromTouchHitTarget(internalInstanceHandle);
442 + // TODO
443 }
444 },
445 };
packages/react-reconciler/src/ReactFiberBeginWork.js
+9 -4
@@ -99,7 +99,8 @@ import type {SuspenseInstance} from './ReactFiberHostConfig';
99 import {
100 pushHostContext,
101 pushHostContainer,
102 - pushHostContextForEvent,
102 + pushHostContextForEventComponent,
103 + pushHostContextForEventTarget,
104 } from './ReactFiberHostContext';
105 import {
106 pushProvider,
@@ -1960,7 +1961,7 @@ function updateEventComponent(current, workInProgress, renderExpirationTime) {
1961 nextChildren,
1962 renderExpirationTime,
1963 );
1963 - pushHostContextForEvent(workInProgress);
1964 + pushHostContextForEventComponent(workInProgress);
1965 return workInProgress.child;
1966 }
1967
@@ -1974,7 +1975,7 @@ function updateEventTarget(current, workInProgress, renderExpirationTime) {
1975 nextChildren,
1976 renderExpirationTime,
1977 );
1977 - pushHostContextForEvent(workInProgress);
1978 + pushHostContextForEventTarget(workInProgress);
1979 return workInProgress.child;
1980 }
1981
@@ -2116,9 +2117,13 @@ function beginWork(
2117 break;
2118 }
2119 case EventComponent:
2120 + if (enableEventAPI) {
2121 + pushHostContextForEventComponent(workInProgress);
2122 + }
2123 + break;
2124 case EventTarget: {
2125 if (enableEventAPI) {
2121 - pushHostContextForEvent(workInProgress);
2126 + pushHostContextForEventTarget(workInProgress);
2127 }
2128 break;
2129 }
packages/react-reconciler/src/ReactFiberCompleteWork.js
+13 -1
@@ -784,7 +784,19 @@ function completeWork(
784 if (enableEventAPI) {
785 popHostContext(workInProgress);
786 const type = workInProgress.type.type;
787 - handleEventTarget(type, newProps, workInProgress);
787 + let node = workInProgress.return;
788 + let parentHostInstance = null;
789 + // Traverse up the fiber tree till we find a host component fiber
790 + while (node !== null) {
791 + if (node.tag === HostComponent) {
792 + parentHostInstance = node.stateNode;
793 + break;
794 + }
795 + node = node.return;
796 + }
797 + if (parentHostInstance !== null) {
798 + handleEventTarget(type, newProps, parentHostInstance, workInProgress);
799 + }
800 }
801 break;
802 }
packages/react-reconciler/src/ReactFiberHostContext.js
+25 -5
@@ -16,7 +16,8 @@ import invariant from 'shared/invariant';
16 import {
17 getChildHostContext,
18 getRootHostContext,
19 - getChildHostContextForEvent,
19 + getChildHostContextForEventComponent,
20 + getChildHostContextForEventTarget,
21 } from './ReactFiberHostConfig';
22 import {createCursor, push, pop} from './ReactFiberStack';
23
@@ -96,10 +97,28 @@ function pushHostContext(fiber: Fiber): void {
97 push(contextStackCursor, nextContext, fiber);
98 }
99
99 -function pushHostContextForEvent(fiber: Fiber): void {
100 +function pushHostContextForEventComponent(fiber: Fiber): void {
101 const context: HostContext = requiredContext(contextStackCursor.current);
101 - const eventTypeof = fiber.type.$$typeof;
102 - const nextContext = getChildHostContextForEvent(context, eventTypeof);
102 + const nextContext = getChildHostContextForEventComponent(context);
103 +
104 + // Don't push this Fiber's context unless it's unique.
105 + if (context === nextContext) {
106 + return;
107 + }
108 +
109 + // Track the context and the Fiber that provided it.
110 + // This enables us to pop only Fibers that provide unique contexts.
111 + push(contextFiberStackCursor, fiber, fiber);
112 + push(contextStackCursor, nextContext, fiber);
113 +}
114 +
115 +function pushHostContextForEventTarget(fiber: Fiber): void {
116 + const context: HostContext = requiredContext(contextStackCursor.current);
117 + const eventTargetType = fiber.type.type;
118 + const nextContext = getChildHostContextForEventTarget(
119 + context,
120 + eventTargetType,
121 + );
122
123 // Don't push this Fiber's context unless it's unique.
124 if (context === nextContext) {
@@ -130,5 +149,6 @@ export {
149 popHostContext,
150 pushHostContainer,
151 pushHostContext,
133 - pushHostContextForEvent,
152 + pushHostContextForEventComponent,
153 + pushHostContextForEventTarget,
154 };
packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js
+51 -126
@@ -19,7 +19,7 @@ let ReactDOM;
19 let ReactDOMServer;
20 let ReactTestUtils;
21 let EventTarget;
22 -let ReactEvents;
22 +let ReactSymbols;
23
24 const noOpResponder = {
25 targetEventTypes: [],
@@ -28,19 +28,26 @@ const noOpResponder = {
28
29 function createReactEventComponent() {
30 return {
31 - $$typeof: Symbol.for('react.event_component'),
31 + $$typeof: ReactSymbols.REACT_EVENT_COMPONENT_TYPE,
32 props: null,
33 responder: noOpResponder,
34 };
35 }
36
37 +function createReactEventTarget() {
38 + return {
39 + $$typeof: ReactSymbols.REACT_EVENT_TARGET_TYPE,
40 + type: Symbol.for('react.event_target.test'),
41 + };
42 +}
43 +
44 function init() {
45 jest.resetModules();
46 ReactFeatureFlags = require('shared/ReactFeatureFlags');
47 ReactFeatureFlags.enableEventAPI = true;
48 React = require('react');
49 Scheduler = require('scheduler');
43 - ReactEvents = require('react-events');
50 + ReactSymbols = require('shared/ReactSymbols');
51 }
52
53 function initNoopRenderer() {
@@ -71,7 +78,7 @@ describe('ReactFiberEvents', () => {
78 beforeEach(() => {
79 initNoopRenderer();
80 EventComponent = createReactEventComponent();
74 - EventTarget = ReactEvents.TouchHitTarget;
81 + EventTarget = createReactEventTarget();
82 });
83
84 it('should render a simple event component with a single child', () => {
@@ -139,11 +146,10 @@ describe('ReactFiberEvents', () => {
146 expect(() => {
147 ReactNoop.render(<Test />);
148 expect(Scheduler).toFlushWithoutYielding();
142 - }).toWarnDev([
149 + }).toWarnDev(
150 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
151 'Wrap the child text "Hello world" in an element.',
145 - 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
146 - ]);
152 + );
153 });
154
155 it('should warn when an event target has a direct text child #2', () => {
@@ -159,32 +165,13 @@ describe('ReactFiberEvents', () => {
165 expect(() => {
166 ReactNoop.render(<Test />);
167 expect(Scheduler).toFlushWithoutYielding();
162 - }).toWarnDev([
168 + }).toWarnDev(
169 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
170 'Wrap the child text "Hello world" in an element.',
165 - 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
166 - ]);
167 - });
168 -
169 - it('should warn when an event target has more than one child', () => {
170 - const Test = () => (
171 - <EventComponent>
172 - <EventTarget>
173 - <span>Child 1</span>
174 - <span>Child 2</span>
175 - </EventTarget>
176 - </EventComponent>
177 - );
178 -
179 - expect(() => {
180 - ReactNoop.render(<Test />);
181 - expect(Scheduler).toFlushWithoutYielding();
182 - }).toWarnDev(
183 - 'Warning: <TouchHitTarget> must only have a single DOM element as a child. Found many children.',
171 );
172 });
173
187 - it('should warn if an event target is not a direct child of an event component', () => {
174 + it('should not warn if an event target is not a direct child of an event component', () => {
175 const Test = () => (
176 <EventComponent>
177 <div>
@@ -195,11 +182,12 @@ describe('ReactFiberEvents', () => {
182 </EventComponent>
183 );
184
198 - expect(() => {
199 - ReactNoop.render(<Test />);
200 - expect(Scheduler).toFlushWithoutYielding();
201 - }).toWarnDev(
202 - 'Warning: validateDOMNesting: React event targets must be direct children of event components.',
185 + ReactNoop.render(<Test />);
186 + expect(Scheduler).toFlushWithoutYielding();
187 + expect(ReactNoop).toMatchRenderedOutput(
188 + <div>
189 + <span>Child 1</span>
190 + </div>,
191 );
192 });
193
@@ -319,7 +307,7 @@ describe('ReactFiberEvents', () => {
307 _updateCounter = updateCounter;
308
309 if (counter === 1) {
322 - return null;
310 + return 'Text!';
311 }
312
313 return (
@@ -351,7 +339,8 @@ describe('ReactFiberEvents', () => {
339 });
340 expect(Scheduler).toFlushWithoutYielding();
341 }).toWarnDev(
354 - 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
342 + 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
343 + 'Wrap the child text "Text!" in an element.',
344 );
345 });
346
@@ -409,7 +398,7 @@ describe('ReactFiberEvents', () => {
398 beforeEach(() => {
399 initTestRenderer();
400 EventComponent = createReactEventComponent();
412 - EventTarget = ReactEvents.TouchHitTarget;
401 + EventTarget = createReactEventTarget();
402 });
403
404 it('should render a simple event component with a single child', () => {
@@ -494,11 +483,10 @@ describe('ReactFiberEvents', () => {
483 expect(() => {
484 root.update(<Test />);
485 expect(Scheduler).toFlushWithoutYielding();
497 - }).toWarnDev([
486 + }).toWarnDev(
487 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
488 'Wrap the child text "Hello world" in an element.',
500 - 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
501 - ]);
489 + );
490 });
491
492 it('should warn when an event target has a direct text child #2', () => {
@@ -515,44 +503,13 @@ describe('ReactFiberEvents', () => {
503 expect(() => {
504 root.update(<Test />);
505 expect(Scheduler).toFlushWithoutYielding();
518 - }).toWarnDev([
506 + }).toWarnDev(
507 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
508 'Wrap the child text "Hello world" in an element.',
521 - 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
522 - ]);
523 - });
524 -
525 - it('should warn when an event target has more than one child', () => {
526 - const Test = () => (
527 - <EventComponent>
528 - <EventTarget>
529 - <span>Child 1</span>
530 - <span>Child 2</span>
531 - </EventTarget>
532 - </EventComponent>
533 - );
534 -
535 - const root = ReactTestRenderer.create(null);
536 - expect(() => {
537 - root.update(<Test />);
538 - expect(Scheduler).toFlushWithoutYielding();
539 - }).toWarnDev(
540 - 'Warning: <TouchHitTarget> must only have a single DOM element as a child. Found many children.',
541 - );
542 - // This should not fire a warning, as this is now valid.
543 - const Test2 = () => (
544 - <EventComponent>
545 - <EventTarget>
546 - <span>Child 1</span>
547 - </EventTarget>
548 - </EventComponent>
509 );
550 - root.update(<Test2 />);
551 - expect(Scheduler).toFlushWithoutYielding();
552 - expect(root).toMatchRenderedOutput(<span>Child 1</span>);
510 });
511
555 - it('should warn if an event target is not a direct child of an event component', () => {
512 + it('should not warn if an event target is not a direct child of an event component', () => {
513 const Test = () => (
514 <EventComponent>
515 <div>
@@ -564,11 +521,12 @@ describe('ReactFiberEvents', () => {
521 );
522
523 const root = ReactTestRenderer.create(null);
567 - expect(() => {
568 - root.update(<Test />);
569 - expect(Scheduler).toFlushWithoutYielding();
570 - }).toWarnDev(
571 - 'Warning: validateDOMNesting: React event targets must be direct children of event components.',
524 + root.update(<Test />);
525 + expect(Scheduler).toFlushWithoutYielding();
526 + expect(root).toMatchRenderedOutput(
527 + <div>
528 + <span>Child 1</span>
529 + </div>,
530 );
531 });
532
@@ -690,7 +648,7 @@ describe('ReactFiberEvents', () => {
648 _updateCounter = updateCounter;
649
650 if (counter === 1) {
693 - return null;
651 + return 'Text!';
652 }
653
654 return (
@@ -722,7 +680,8 @@ describe('ReactFiberEvents', () => {
680 _updateCounter(counter => counter + 1);
681 });
682 }).toWarnDev(
725 - 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
683 + 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
684 + 'Wrap the child text "Text!" in an element.',
685 );
686 });
687
@@ -781,7 +740,7 @@ describe('ReactFiberEvents', () => {
740 beforeEach(() => {
741 initReactDOM();
742 EventComponent = createReactEventComponent();
784 - EventTarget = ReactEvents.TouchHitTarget;
743 + EventTarget = createReactEventTarget();
744 });
745
746 it('should render a simple event component with a single child', () => {
@@ -865,11 +824,10 @@ describe('ReactFiberEvents', () => {
824 const container = document.createElement('div');
825 ReactDOM.render(<Test />, container);
826 expect(Scheduler).toFlushWithoutYielding();
868 - }).toWarnDev([
827 + }).toWarnDev(
828 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
829 'Wrap the child text "Hello world" in an element.',
871 - 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
872 - ]);
830 + );
831 });
832
833 it('should warn when an event target has a direct text child #2', () => {
@@ -886,44 +844,13 @@ describe('ReactFiberEvents', () => {
844 const container = document.createElement('div');
845 ReactDOM.render(<Test />, container);
846 expect(Scheduler).toFlushWithoutYielding();
889 - }).toWarnDev([
847 + }).toWarnDev(
848 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
849 'Wrap the child text "Hello world" in an element.',
892 - 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
893 - ]);
894 - });
895 -
896 - it('should warn when an event target has more than one child', () => {
897 - const Test = () => (
898 - <EventComponent>
899 - <EventTarget>
900 - <span>Child 1</span>
901 - <span>Child 2</span>
902 - </EventTarget>
903 - </EventComponent>
850 );
905 -
906 - const container = document.createElement('div');
907 - expect(() => {
908 - ReactDOM.render(<Test />, container);
909 - expect(Scheduler).toFlushWithoutYielding();
910 - }).toWarnDev(
911 - 'Warning: <TouchHitTarget> must only have a single DOM element as a child. Found many children.',
912 - );
913 - // This should not fire a warning, as this is now valid.
914 - const Test2 = () => (
915 - <EventComponent>
916 - <EventTarget>
917 - <span>Child 1</span>
918 - </EventTarget>
919 - </EventComponent>
920 - );
921 - ReactDOM.render(<Test2 />, container);
922 - expect(Scheduler).toFlushWithoutYielding();
923 - expect(container.innerHTML).toBe('<span>Child 1</span>');
851 });
852
926 - it('should warn if an event target is not a direct child of an event component', () => {
853 + it('should not warn if an event target is not a direct child of an event component', () => {
854 const Test = () => (
855 <EventComponent>
856 <div>
@@ -934,13 +861,10 @@ describe('ReactFiberEvents', () => {
861 </EventComponent>
862 );
863
937 - expect(() => {
938 - const container = document.createElement('div');
939 - ReactDOM.render(<Test />, container);
940 - expect(Scheduler).toFlushWithoutYielding();
941 - }).toWarnDev(
942 - 'Warning: validateDOMNesting: React event targets must be direct children of event components.',
943 - );
864 + const container = document.createElement('div');
865 + ReactDOM.render(<Test />, container);
866 + expect(Scheduler).toFlushWithoutYielding();
867 + expect(container.innerHTML).toBe('<div><span>Child 1</span></div>');
868 });
869
870 it('should warn if an event target has an event component as a child', () => {
@@ -1052,7 +976,7 @@ describe('ReactFiberEvents', () => {
976 _updateCounter = updateCounter;
977
978 if (counter === 1) {
1055 - return null;
979 + return 'Text!';
980 }
981
982 return (
@@ -1080,7 +1004,8 @@ describe('ReactFiberEvents', () => {
1004 });
1005 expect(Scheduler).toFlushWithoutYielding();
1006 }).toWarnDev(
1083 - 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
1007 + 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
1008 + 'Wrap the child text "Text!" in an element.',
1009 );
1010 });
1011
@@ -1134,7 +1059,7 @@ describe('ReactFiberEvents', () => {
1059 beforeEach(() => {
1060 initReactDOMServer();
1061 EventComponent = createReactEventComponent();
1137 - EventTarget = ReactEvents.TouchHitTarget;
1062 + EventTarget = createReactEventTarget();
1063 });
1064
1065 it('should render a simple event component with a single child', () => {
packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js
+4 -1
@@ -31,7 +31,10 @@ describe('ReactFiberHostContext', () => {
31 getChildHostContext: function() {
32 return null;
33 },
34 - getChildHostContextForEvent: function() {
34 + getChildHostContextForEventComponent: function() {
35 + return null;
36 + },
37 + getChildHostContextForEventTarget: function() {
38 return null;
39 },
40 shouldSetTextContent: function() {
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
+4 -2
@@ -41,8 +41,10 @@ export opaque type NoTimeout = mixed; // eslint-disable-line no-undef
41 export const getPublicInstance = $$$hostConfig.getPublicInstance;
42 export const getRootHostContext = $$$hostConfig.getRootHostContext;
43 export const getChildHostContext = $$$hostConfig.getChildHostContext;
44 -export const getChildHostContextForEvent =
45 - $$$hostConfig.getChildHostContextForEvent;
44 +export const getChildHostContextForEventComponent =
45 + $$$hostConfig.getChildHostContextForEventComponent;
46 +export const getChildHostContextForEventTarget =
47 + $$$hostConfig.getChildHostContextForEventTarget;
48 export const prepareForCommit = $$$hostConfig.prepareForCommit;
49 export const resetAfterCommit = $$$hostConfig.resetAfterCommit;
50 export const createInstance = $$$hostConfig.createInstance;
packages/react-test-renderer/src/ReactTestHostConfig.js
+35 -19
@@ -10,12 +10,7 @@
10 import warning from 'shared/warning';
11
12 import type {ReactEventResponder} from 'shared/ReactTypes';
13 -import {
14 - REACT_EVENT_COMPONENT_TYPE,
15 - REACT_EVENT_TARGET_TYPE,
16 - REACT_EVENT_TARGET_TOUCH_HIT,
17 -} from 'shared/ReactSymbols';
18 -import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget';
13 +import {REACT_EVENT_TARGET_TOUCH_HIT} from 'shared/ReactSymbols';
14
15 import {enableEventAPI} from 'shared/ReactFeatureFlags';
16
@@ -52,6 +47,7 @@ export * from 'shared/HostConfigWithNoHydration';
47
48 const EVENT_COMPONENT_CONTEXT = {};
49 const EVENT_TARGET_CONTEXT = {};
50 +const EVENT_TOUCH_HIT_TARGET_CONTEXT = {};
51 const NO_CONTEXT = {};
52 const UPDATE_SIGNAL = {};
53 if (__DEV__) {
@@ -127,24 +123,34 @@ export function getChildHostContext(
123 return NO_CONTEXT;
124 }
125
130 -export function getChildHostContextForEvent(
126 +export function getChildHostContextForEventComponent(
127 + parentHostContext: HostContext,
128 +): HostContext {
129 + if (__DEV__ && enableEventAPI) {
130 + warning(
131 + parentHostContext !== EVENT_TARGET_CONTEXT &&
132 + parentHostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
133 + 'validateDOMNesting: React event targets must not have event components as children.',
134 + );
135 + return EVENT_COMPONENT_CONTEXT;
136 + }
137 + return NO_CONTEXT;
138 +}
139 +
140 +export function getChildHostContextForEventTarget(
141 parentHostContext: HostContext,
142 type: Symbol | number,
143 ): HostContext {
144 if (__DEV__ && enableEventAPI) {
135 - if (type === REACT_EVENT_COMPONENT_TYPE) {
145 + if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
146 warning(
137 - parentHostContext !== EVENT_TARGET_CONTEXT,
138 - 'validateDOMNesting: React event targets must not have event components as children.',
147 + parentHostContext !== EVENT_COMPONENT_CONTEXT,
148 + 'validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
149 + 'Ensure <TouchHitTarget> is a direct child of a DOM element.',
150 );
140 - return EVENT_COMPONENT_CONTEXT;
141 - } else if (type === REACT_EVENT_TARGET_TYPE) {
142 - warning(
143 - parentHostContext === EVENT_COMPONENT_CONTEXT,
144 - 'validateDOMNesting: React event targets must be direct children of event components.',
145 - );
146 - return EVENT_TARGET_CONTEXT;
151 + return EVENT_TOUCH_HIT_TARGET_CONTEXT;
152 }
153 + return EVENT_TARGET_CONTEXT;
154 }
155 return NO_CONTEXT;
156 }
@@ -164,6 +170,12 @@ export function createInstance(
170 hostContext: Object,
171 internalInstanceHandle: Object,
172 ): Instance {
173 + if (__DEV__ && enableEventAPI) {
174 + warning(
175 + hostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
176 + 'validateDOMNesting: <TouchHitTarget> must not have any children.',
177 + );
178 + }
179 return {
180 type,
181 props,
@@ -221,6 +233,10 @@ export function createTextInstance(
233 internalInstanceHandle: Object,
234 ): TextInstance {
235 if (__DEV__ && enableEventAPI) {
236 + warning(
237 + hostContext !== EVENT_TOUCH_HIT_TARGET_CONTEXT,
238 + 'validateDOMNesting: <TouchHitTarget> must not have any children.',
239 + );
240 warning(
241 hostContext !== EVENT_COMPONENT_CONTEXT,
242 'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
@@ -320,10 +336,10 @@ export function handleEventComponent(
336 export function handleEventTarget(
337 type: Symbol | number,
338 props: Props,
339 + parentInstance: Container,
340 internalInstanceHandle: Object,
341 ) {
342 if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
326 - // Validates that there is a single element
327 - getElementFromTouchHitTarget(internalInstanceHandle);
343 + // TODO
344 }
345 }
packages/shared/getElementFromTouchHitTarget.js deleted
-85
@@ -1,85 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {Fiber} from 'react-reconciler/src/ReactFiber';
11 -
12 -import {HostComponent} from 'shared/ReactWorkTags';
13 -import warning from 'shared/warning';
14 -
15 -type HostContext = Object;
16 -
17 -type TextInstance =
18 - | Text
19 - | {|
20 - text: string,
21 - id: number,
22 - hidden: boolean,
23 - context: HostContext,
24 - |};
25 -
26 -type Instance =
27 - | Element
28 - | {|
29 - type: string,
30 - id: number,
31 - children: Array<Instance | TextInstance>,
32 - text: string | null,
33 - prop: any,
34 - hidden: boolean,
35 - context: HostContext,
36 - |};
37 -
38 -export default function getElementFromTouchHitTarget(
39 - targetFiber: Fiber,
40 -): null | Instance {
41 - // Traverse through child fibers and find the first host components
42 - let node = targetFiber.child;
43 - let hostComponent = null;
44 -
45 - while (node !== null) {
46 - if (node.tag === HostComponent) {
47 - if (__DEV__) {
48 - if (hostComponent === null) {
49 - hostComponent = node.stateNode;
50 - } else {
51 - warning(
52 - false,
53 - '<TouchHitTarget> must only have a single DOM element as a child. ' +
54 - 'Found many children.',
55 - );
56 - }
57 - while (node !== null) {
58 - if (node === targetFiber) {
59 - return hostComponent;
60 - } else if (node.sibling !== null) {
61 - node = node.sibling;
62 - break;
63 - }
64 - node = node.return;
65 - }
66 - } else {
67 - return node.stateNode;
68 - }
69 - } else if (node.child !== null) {
70 - node = node.child;
71 - } else if (node.sibling !== null) {
72 - node = node.sibling;
73 - } else {
74 - break;
75 - }
76 - }
77 - if (__DEV__) {
78 - warning(
79 - hostComponent !== null,
80 - '<TouchHitTarget> must have a single DOM element as a child. ' +
81 - 'Found no children.',
82 - );
83 - }
84 - return hostComponent;
85 -}