@samitouri / QOS-React / commits / 725c054d4d

Refactor findHostInstance and findNodeHandle (#12575)

* Move findNodeHandle into the renderers and use instantiation This is just like ReactDOM does it. This also lets us get rid of injection for findNodeHandle. Instead I move NativeMethodsMixin and ReactNativeComponent to use instantiation. * Refactor findHostInstance The reconciler shouldn't expose the Fiber data structure. We should pass the component instance to the reconciler, since the reconciler is the thing that is supposed to be instancemap aware. * Fix devtools injection

Sebastian Markbåge committed Apr 9, 2018 at 20:15 UTC 725c054d4d5d07c5c553a1ca724b01f2e6a43c5d
10 files changed +446 -484
packages/react-dom/src/__tests__/findDOMNode-test.js
+1 -3
@@ -65,9 +65,7 @@ describe('findDOMNode', () => {
65 it('findDOMNode should reject random objects', () => {
66 expect(function() {
67 ReactDOM.findDOMNode({foo: 'bar'});
68 - }).toThrowError(
69 - 'Element appears to be neither ReactComponent nor DOMNode. Keys: foo',
70 - );
68 + }).toThrowError('Argument appears to not be a ReactComponent. Keys: foo');
69 });
70
71 it('findDOMNode should reject unmounted objects with render func', () => {
packages/react-dom/src/client/ReactDOM.js
+1 -14
@@ -1147,20 +1147,7 @@ const ReactDOM: Object = {
1147 return (componentOrElement: any);
1148 }
1149
1150 - const inst = ReactInstanceMap.get(componentOrElement);
1151 - if (inst) {
1152 - return DOMRenderer.findHostInstance(inst);
1153 - }
1154 -
1155 - if (typeof componentOrElement.render === 'function') {
1156 - invariant(false, 'Unable to find node on an unmounted component.');
1157 - } else {
1158 - invariant(
1159 - false,
1160 - 'Element appears to be neither ReactComponent nor DOMNode. Keys: %s',
1161 - Object.keys(componentOrElement),
1162 - );
1163 - }
1150 + return DOMRenderer.findHostInstance(componentOrElement);
1151 },
1152
1153 hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
packages/react-native-renderer/src/NativeMethodsMixin.js
+184 -179
@@ -26,192 +26,197 @@ import {
26 throwOnStylesProp,
27 warnForStyleProps,
28 } from './NativeMethodsMixinUtils';
29 -import findNodeHandle from './findNodeHandle';
30 -import findNumericNodeHandle from './findNumericNodeHandle';
29
32 -/**
33 - * `NativeMethodsMixin` provides methods to access the underlying native
34 - * component directly. This can be useful in cases when you want to focus
35 - * a view or measure its on-screen dimensions, for example.
36 - *
37 - * The methods described here are available on most of the default components
38 - * provided by React Native. Note, however, that they are *not* available on
39 - * composite components that aren't directly backed by a native view. This will
40 - * generally include most components that you define in your own app. For more
41 - * information, see [Direct
42 - * Manipulation](docs/direct-manipulation.html).
43 - *
44 - * Note the Flow $Exact<> syntax is required to support mixins.
45 - * React createClass mixins can only be used with exact types.
46 - */
47 -const NativeMethodsMixin: $Exact<NativeMethodsMixinType> = {
30 +export default function(
31 + findNodeHandle: any => ?number,
32 + findHostInstance: any => any,
33 +) {
34 /**
49 - * Determines the location on screen, width, and height of the given view and
50 - * returns the values via an async callback. If successful, the callback will
51 - * be called with the following arguments:
52 - *
53 - * - x
54 - * - y
55 - * - width
56 - * - height
57 - * - pageX
58 - * - pageY
35 + * `NativeMethodsMixin` provides methods to access the underlying native
36 + * component directly. This can be useful in cases when you want to focus
37 + * a view or measure its on-screen dimensions, for example.
38 *
60 - * Note that these measurements are not available until after the rendering
61 - * has been completed in native. If you need the measurements as soon as
62 - * possible, consider using the [`onLayout`
63 - * prop](docs/view.html#onlayout) instead.
64 - */
65 - measure: function(callback: MeasureOnSuccessCallback) {
66 - UIManager.measure(
67 - findNumericNodeHandle(this),
68 - mountSafeCallback(this, callback),
69 - );
70 - },
71 -
72 - /**
73 - * Determines the location of the given view in the window and returns the
74 - * values via an async callback. If the React root view is embedded in
75 - * another native view, this will give you the absolute coordinates. If
76 - * successful, the callback will be called with the following
77 - * arguments:
39 + * The methods described here are available on most of the default components
40 + * provided by React Native. Note, however, that they are *not* available on
41 + * composite components that aren't directly backed by a native view. This will
42 + * generally include most components that you define in your own app. For more
43 + * information, see [Direct
44 + * Manipulation](docs/direct-manipulation.html).
45 *
79 - * - x
80 - * - y
81 - * - width
82 - * - height
83 - *
84 - * Note that these measurements are not available until after the rendering
85 - * has been completed in native.
86 - */
87 - measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) {
88 - UIManager.measureInWindow(
89 - findNumericNodeHandle(this),
90 - mountSafeCallback(this, callback),
91 - );
92 - },
93 -
94 - /**
95 - * Like [`measure()`](#measure), but measures the view relative an ancestor,
96 - * specified as `relativeToNativeNode`. This means that the returned x, y
97 - * are relative to the origin x, y of the ancestor view.
98 - *
99 - * As always, to obtain a native node handle for a component, you can use
100 - * `findNumericNodeHandle(component)`.
101 - */
102 - measureLayout: function(
103 - relativeToNativeNode: number,
104 - onSuccess: MeasureLayoutOnSuccessCallback,
105 - onFail: () => void /* currently unused */,
106 - ) {
107 - UIManager.measureLayout(
108 - findNumericNodeHandle(this),
109 - relativeToNativeNode,
110 - mountSafeCallback(this, onFail),
111 - mountSafeCallback(this, onSuccess),
112 - );
113 - },
114 -
115 - /**
116 - * This function sends props straight to native. They will not participate in
117 - * future diff process - this means that if you do not include them in the
118 - * next render, they will remain active (see [Direct
119 - * Manipulation](docs/direct-manipulation.html)).
46 + * Note the Flow $Exact<> syntax is required to support mixins.
47 + * React createClass mixins can only be used with exact types.
48 */
121 - setNativeProps: function(nativeProps: Object) {
122 - // Class components don't have viewConfig -> validateAttributes.
123 - // Nor does it make sense to set native props on a non-native component.
124 - // Instead, find the nearest host component and set props on it.
125 - // Use findNodeHandle() rather than findNumericNodeHandle() because
126 - // We want the instance/wrapper (not the native tag).
127 - let maybeInstance;
128 -
129 - // Fiber errors if findNodeHandle is called for an umounted component.
130 - // Tests using ReactTestRenderer will trigger this case indirectly.
131 - // Mimicking stack behavior, we should silently ignore this case.
132 - // TODO Fix ReactTestRenderer so we can remove this try/catch.
133 - try {
134 - maybeInstance = findNodeHandle(this);
135 - } catch (error) {}
136 -
137 - // If there is no host component beneath this we should fail silently.
138 - // This is not an error; it could mean a class component rendered null.
139 - if (maybeInstance == null) {
140 - return;
141 - }
142 -
143 - const viewConfig: ReactNativeBaseComponentViewConfig =
144 - maybeInstance.viewConfig;
145 -
146 - if (__DEV__) {
147 - warnForStyleProps(nativeProps, viewConfig.validAttributes);
148 - }
149 -
150 - const updatePayload = ReactNativeAttributePayload.create(
151 - nativeProps,
152 - viewConfig.validAttributes,
153 - );
154 -
155 - // Avoid the overhead of bridge calls if there's no update.
156 - // This is an expensive no-op for Android, and causes an unnecessary
157 - // view invalidation for certain components (eg RCTTextInput) on iOS.
158 - if (updatePayload != null) {
159 - UIManager.updateView(
160 - maybeInstance._nativeTag,
161 - viewConfig.uiViewClassName,
162 - updatePayload,
49 + const NativeMethodsMixin: $Exact<NativeMethodsMixinType> = {
50 + /**
51 + * Determines the location on screen, width, and height of the given view and
52 + * returns the values via an async callback. If successful, the callback will
53 + * be called with the following arguments:
54 + *
55 + * - x
56 + * - y
57 + * - width
58 + * - height
59 + * - pageX
60 + * - pageY
61 + *
62 + * Note that these measurements are not available until after the rendering
63 + * has been completed in native. If you need the measurements as soon as
64 + * possible, consider using the [`onLayout`
65 + * prop](docs/view.html#onlayout) instead.
66 + */
67 + measure: function(callback: MeasureOnSuccessCallback) {
68 + UIManager.measure(
69 + findNodeHandle(this),
70 + mountSafeCallback(this, callback),
71 + );
72 + },
73 +
74 + /**
75 + * Determines the location of the given view in the window and returns the
76 + * values via an async callback. If the React root view is embedded in
77 + * another native view, this will give you the absolute coordinates. If
78 + * successful, the callback will be called with the following
79 + * arguments:
80 + *
81 + * - x
82 + * - y
83 + * - width
84 + * - height
85 + *
86 + * Note that these measurements are not available until after the rendering
87 + * has been completed in native.
88 + */
89 + measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) {
90 + UIManager.measureInWindow(
91 + findNodeHandle(this),
92 + mountSafeCallback(this, callback),
93 + );
94 + },
95 +
96 + /**
97 + * Like [`measure()`](#measure), but measures the view relative an ancestor,
98 + * specified as `relativeToNativeNode`. This means that the returned x, y
99 + * are relative to the origin x, y of the ancestor view.
100 + *
101 + * As always, to obtain a native node handle for a component, you can use
102 + * `findNodeHandle(component)`.
103 + */
104 + measureLayout: function(
105 + relativeToNativeNode: number,
106 + onSuccess: MeasureLayoutOnSuccessCallback,
107 + onFail: () => void /* currently unused */,
108 + ) {
109 + UIManager.measureLayout(
110 + findNodeHandle(this),
111 + relativeToNativeNode,
112 + mountSafeCallback(this, onFail),
113 + mountSafeCallback(this, onSuccess),
114 + );
115 + },
116 +
117 + /**
118 + * This function sends props straight to native. They will not participate in
119 + * future diff process - this means that if you do not include them in the
120 + * next render, they will remain active (see [Direct
121 + * Manipulation](docs/direct-manipulation.html)).
122 + */
123 + setNativeProps: function(nativeProps: Object) {
124 + // Class components don't have viewConfig -> validateAttributes.
125 + // Nor does it make sense to set native props on a non-native component.
126 + // Instead, find the nearest host component and set props on it.
127 + // Use findNodeHandle() rather than findNodeHandle() because
128 + // We want the instance/wrapper (not the native tag).
129 + let maybeInstance;
130 +
131 + // Fiber errors if findNodeHandle is called for an umounted component.
132 + // Tests using ReactTestRenderer will trigger this case indirectly.
133 + // Mimicking stack behavior, we should silently ignore this case.
134 + // TODO Fix ReactTestRenderer so we can remove this try/catch.
135 + try {
136 + maybeInstance = findHostInstance(this);
137 + } catch (error) {}
138 +
139 + // If there is no host component beneath this we should fail silently.
140 + // This is not an error; it could mean a class component rendered null.
141 + if (maybeInstance == null) {
142 + return;
143 + }
144 +
145 + const viewConfig: ReactNativeBaseComponentViewConfig =
146 + maybeInstance.viewConfig;
147 +
148 + if (__DEV__) {
149 + warnForStyleProps(nativeProps, viewConfig.validAttributes);
150 + }
151 +
152 + const updatePayload = ReactNativeAttributePayload.create(
153 + nativeProps,
154 + viewConfig.validAttributes,
155 );
164 - }
165 - },
166 -
167 - /**
168 - * Requests focus for the given input or view. The exact behavior triggered
169 - * will depend on the platform and type of view.
170 - */
171 - focus: function() {
172 - TextInputState.focusTextInput(findNumericNodeHandle(this));
173 - },
156
175 - /**
176 - * Removes focus from an input or view. This is the opposite of `focus()`.
177 - */
178 - blur: function() {
179 - TextInputState.blurTextInput(findNumericNodeHandle(this));
180 - },
181 -};
182 -
183 -if (__DEV__) {
184 - // hide this from Flow since we can't define these properties outside of
185 - // __DEV__ without actually implementing them (setting them to undefined
186 - // isn't allowed by ReactClass)
187 - const NativeMethodsMixin_DEV = (NativeMethodsMixin: any);
188 - invariant(
189 - !NativeMethodsMixin_DEV.componentWillMount &&
190 - !NativeMethodsMixin_DEV.componentWillReceiveProps &&
191 - !NativeMethodsMixin_DEV.UNSAFE_componentWillMount &&
192 - !NativeMethodsMixin_DEV.UNSAFE_componentWillReceiveProps,
193 - 'Do not override existing functions.',
194 - );
195 - // TODO (bvaughn) Remove cWM and cWRP in a future version of React Native,
196 - // Once these lifecycles have been remove from the reconciler.
197 - NativeMethodsMixin_DEV.componentWillMount = function() {
198 - throwOnStylesProp(this, this.props);
199 - };
200 - NativeMethodsMixin_DEV.componentWillReceiveProps = function(newProps) {
201 - throwOnStylesProp(this, newProps);
202 - };
203 - NativeMethodsMixin_DEV.UNSAFE_componentWillMount = function() {
204 - throwOnStylesProp(this, this.props);
205 - };
206 - NativeMethodsMixin_DEV.UNSAFE_componentWillReceiveProps = function(newProps) {
207 - throwOnStylesProp(this, newProps);
157 + // Avoid the overhead of bridge calls if there's no update.
158 + // This is an expensive no-op for Android, and causes an unnecessary
159 + // view invalidation for certain components (eg RCTTextInput) on iOS.
160 + if (updatePayload != null) {
161 + UIManager.updateView(
162 + maybeInstance._nativeTag,
163 + viewConfig.uiViewClassName,
164 + updatePayload,
165 + );
166 + }
167 + },
168 +
169 + /**
170 + * Requests focus for the given input or view. The exact behavior triggered
171 + * will depend on the platform and type of view.
172 + */
173 + focus: function() {
174 + TextInputState.focusTextInput(findNodeHandle(this));
175 + },
176 +
177 + /**
178 + * Removes focus from an input or view. This is the opposite of `focus()`.
179 + */
180 + blur: function() {
181 + TextInputState.blurTextInput(findNodeHandle(this));
182 + },
183 };
184
210 - // React may warn about cWM/cWRP/cWU methods being deprecated.
211 - // Add a flag to suppress these warnings for this special case.
212 - // TODO (bvaughn) Remove this flag once the above methods have been removed.
213 - NativeMethodsMixin_DEV.componentWillMount.__suppressDeprecationWarning = true;
214 - NativeMethodsMixin_DEV.componentWillReceiveProps.__suppressDeprecationWarning = true;
185 + if (__DEV__) {
186 + // hide this from Flow since we can't define these properties outside of
187 + // __DEV__ without actually implementing them (setting them to undefined
188 + // isn't allowed by ReactClass)
189 + const NativeMethodsMixin_DEV = (NativeMethodsMixin: any);
190 + invariant(
191 + !NativeMethodsMixin_DEV.componentWillMount &&
192 + !NativeMethodsMixin_DEV.componentWillReceiveProps &&
193 + !NativeMethodsMixin_DEV.UNSAFE_componentWillMount &&
194 + !NativeMethodsMixin_DEV.UNSAFE_componentWillReceiveProps,
195 + 'Do not override existing functions.',
196 + );
197 + // TODO (bvaughn) Remove cWM and cWRP in a future version of React Native,
198 + // Once these lifecycles have been remove from the reconciler.
199 + NativeMethodsMixin_DEV.componentWillMount = function() {
200 + throwOnStylesProp(this, this.props);
201 + };
202 + NativeMethodsMixin_DEV.componentWillReceiveProps = function(newProps) {
203 + throwOnStylesProp(this, newProps);
204 + };
205 + NativeMethodsMixin_DEV.UNSAFE_componentWillMount = function() {
206 + throwOnStylesProp(this, this.props);
207 + };
208 + NativeMethodsMixin_DEV.UNSAFE_componentWillReceiveProps = function(
209 + newProps,
210 + ) {
211 + throwOnStylesProp(this, newProps);
212 + };
213 +
214 + // React may warn about cWM/cWRP/cWU methods being deprecated.
215 + // Add a flag to suppress these warnings for this special case.
216 + // TODO (bvaughn) Remove this flag once the above methods have been removed.
217 + NativeMethodsMixin_DEV.componentWillMount.__suppressDeprecationWarning = true;
218 + NativeMethodsMixin_DEV.componentWillReceiveProps.__suppressDeprecationWarning = true;
219 + }
220 +
221 + return NativeMethodsMixin;
222 }
216 -
217 -export default NativeMethodsMixin;
packages/react-native-renderer/src/ReactFabric.js
+49 -6
@@ -21,19 +21,62 @@ import ReactNativeComponent from './ReactNativeComponent';
21 import * as ReactNativeComponentTree from './ReactNativeComponentTree';
22 import ReactFabricRenderer from './ReactFabricRenderer';
23 import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
24 -import {injectFindHostInstance} from './findNodeHandle';
25 -import findNumericNodeHandle from './findNumericNodeHandle';
24
27 -injectFindHostInstance(ReactFabricRenderer.findHostInstance);
25 +import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
26 +import getComponentName from 'shared/getComponentName';
27 +import warning from 'fbjs/lib/warning';
28 +
29 +const findHostInstance = ReactFabricRenderer.findHostInstance;
30 +
31 +function findNodeHandle(componentOrHandle: any): ?number {
32 + if (__DEV__) {
33 + const owner = ReactCurrentOwner.current;
34 + if (owner !== null && owner.stateNode !== null) {
35 + warning(
36 + owner.stateNode._warnedAboutRefsInRender,
37 + '%s is accessing findNodeHandle inside its render(). ' +
38 + 'render() should be a pure function of props and state. It should ' +
39 + 'never access something that requires stale data from the previous ' +
40 + 'render, such as refs. Move this logic to componentDidMount and ' +
41 + 'componentDidUpdate instead.',
42 + getComponentName(owner) || 'A component',
43 + );
44 +
45 + owner.stateNode._warnedAboutRefsInRender = true;
46 + }
47 + }
48 + if (componentOrHandle == null) {
49 + return null;
50 + }
51 + if (typeof componentOrHandle === 'number') {
52 + // Already a node handle
53 + return componentOrHandle;
54 + }
55 + if (componentOrHandle._nativeTag) {
56 + return componentOrHandle._nativeTag;
57 + }
58 + if (componentOrHandle.canonical && componentOrHandle.canonical._nativeTag) {
59 + return componentOrHandle.canonical._nativeTag;
60 + }
61 + const hostInstance = findHostInstance(componentOrHandle);
62 + if (hostInstance == null) {
63 + return hostInstance;
64 + }
65 + if (hostInstance.canonical) {
66 + // Fabric
67 + return hostInstance.canonical._nativeTag;
68 + }
69 + return hostInstance._nativeTag;
70 +}
71
72 ReactGenericBatching.injection.injectRenderer(ReactFabricRenderer);
73
74 const roots = new Map();
75
76 const ReactFabric: ReactFabricType = {
34 - NativeComponent: ReactNativeComponent,
77 + NativeComponent: ReactNativeComponent(findNodeHandle, findHostInstance),
78
36 - findNodeHandle: findNumericNodeHandle,
79 + findNodeHandle,
80
81 render(element: React$Element<any>, containerTag: any, callback: ?Function) {
82 let root = roots.get(containerTag);
@@ -69,7 +112,7 @@ const ReactFabric: ReactFabricType = {
112
113 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
114 // Used as a mixin in many createClass-based components
72 - NativeMethodsMixin,
115 + NativeMethodsMixin: NativeMethodsMixin(findNodeHandle, findHostInstance),
116 // Used by react-native-github/Libraries/ components
117 ReactNativeComponentTree, // ScrollResponder
118 },
packages/react-native-renderer/src/ReactNativeComponent.js
+141 -138
@@ -23,156 +23,159 @@ import UIManager from 'UIManager';
23
24 import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
25 import {mountSafeCallback} from './NativeMethodsMixinUtils';
26 -import findNodeHandle from './findNodeHandle';
27 -import findNumericNodeHandle from './findNumericNodeHandle';
28 -
29 -/**
30 - * Superclass that provides methods to access the underlying native component.
31 - * This can be useful when you want to focus a view or measure its dimensions.
32 - *
33 - * Methods implemented by this class are available on most default components
34 - * provided by React Native. However, they are *not* available on composite
35 - * components that are not directly backed by a native view. For more
36 - * information, see [Direct Manipulation](docs/direct-manipulation.html).
37 - *
38 - * @abstract
39 - */
40 -class ReactNativeComponent<Props, State = void> extends React.Component<
41 - Props,
42 - State,
43 -> {
44 - /**
45 - * Due to bugs in Flow's handling of React.createClass, some fields already
46 - * declared in the base class need to be redeclared below.
47 - */
48 - props: Props;
49 - state: State;
26
27 +export default function(
28 + findNodeHandle: any => ?number,
29 + findHostInstance: any => any,
30 +) {
31 /**
52 - * Removes focus. This is the opposite of `focus()`.
53 - */
54 - blur(): void {
55 - TextInputState.blurTextInput(findNumericNodeHandle(this));
56 - }
57 -
58 - /**
59 - * Requests focus. The exact behavior depends on the platform and view.
60 - */
61 - focus(): void {
62 - TextInputState.focusTextInput(findNumericNodeHandle(this));
63 - }
64 -
65 - /**
66 - * Measures the on-screen location and dimensions. If successful, the callback
67 - * will be called asynchronously with the following arguments:
32 + * Superclass that provides methods to access the underlying native component.
33 + * This can be useful when you want to focus a view or measure its dimensions.
34 *
69 - * - x
70 - * - y
71 - * - width
72 - * - height
73 - * - pageX
74 - * - pageY
35 + * Methods implemented by this class are available on most default components
36 + * provided by React Native. However, they are *not* available on composite
37 + * components that are not directly backed by a native view. For more
38 + * information, see [Direct Manipulation](docs/direct-manipulation.html).
39 *
76 - * These values are not available until after natives rendering completes. If
77 - * you need the measurements as soon as possible, consider using the
78 - * [`onLayout` prop](docs/view.html#onlayout) instead.
40 + * @abstract
41 */
80 - measure(callback: MeasureOnSuccessCallback): void {
81 - UIManager.measure(
82 - findNumericNodeHandle(this),
83 - mountSafeCallback(this, callback),
84 - );
85 - }
42 + class ReactNativeComponent<Props, State = void> extends React.Component<
43 + Props,
44 + State,
45 + > {
46 + /**
47 + * Due to bugs in Flow's handling of React.createClass, some fields already
48 + * declared in the base class need to be redeclared below.
49 + */
50 + props: Props;
51 + state: State;
52 +
53 + /**
54 + * Removes focus. This is the opposite of `focus()`.
55 + */
56 + blur(): void {
57 + TextInputState.blurTextInput(findNodeHandle(this));
58 + }
59
87 - /**
88 - * Measures the on-screen location and dimensions. Even if the React Native
89 - * root view is embedded within another native view, this method will give you
90 - * the absolute coordinates measured from the window. If successful, the
91 - * callback will be called asynchronously with the following arguments:
92 - *
93 - * - x
94 - * - y
95 - * - width
96 - * - height
97 - *
98 - * These values are not available until after natives rendering completes.
99 - */
100 - measureInWindow(callback: MeasureInWindowOnSuccessCallback): void {
101 - UIManager.measureInWindow(
102 - findNumericNodeHandle(this),
103 - mountSafeCallback(this, callback),
104 - );
105 - }
60 + /**
61 + * Requests focus. The exact behavior depends on the platform and view.
62 + */
63 + focus(): void {
64 + TextInputState.focusTextInput(findNodeHandle(this));
65 + }
66
107 - /**
108 - * Similar to [`measure()`](#measure), but the resulting location will be
109 - * relative to the supplied ancestor's location.
110 - *
111 - * Obtain a native node handle with `ReactNative.findNodeHandle(component)`.
112 - */
113 - measureLayout(
114 - relativeToNativeNode: number,
115 - onSuccess: MeasureLayoutOnSuccessCallback,
116 - onFail: () => void /* currently unused */,
117 - ): void {
118 - UIManager.measureLayout(
119 - findNumericNodeHandle(this),
120 - relativeToNativeNode,
121 - mountSafeCallback(this, onFail),
122 - mountSafeCallback(this, onSuccess),
123 - );
124 - }
67 + /**
68 + * Measures the on-screen location and dimensions. If successful, the callback
69 + * will be called asynchronously with the following arguments:
70 + *
71 + * - x
72 + * - y
73 + * - width
74 + * - height
75 + * - pageX
76 + * - pageY
77 + *
78 + * These values are not available until after natives rendering completes. If
79 + * you need the measurements as soon as possible, consider using the
80 + * [`onLayout` prop](docs/view.html#onlayout) instead.
81 + */
82 + measure(callback: MeasureOnSuccessCallback): void {
83 + UIManager.measure(
84 + findNodeHandle(this),
85 + mountSafeCallback(this, callback),
86 + );
87 + }
88
126 - /**
127 - * This function sends props straight to native. They will not participate in
128 - * future diff process - this means that if you do not include them in the
129 - * next render, they will remain active (see [Direct
130 - * Manipulation](docs/direct-manipulation.html)).
131 - */
132 - setNativeProps(nativeProps: Object): void {
133 - // Class components don't have viewConfig -> validateAttributes.
134 - // Nor does it make sense to set native props on a non-native component.
135 - // Instead, find the nearest host component and set props on it.
136 - // Use findNodeHandle() rather than ReactNative.findNodeHandle() because
137 - // We want the instance/wrapper (not the native tag).
138 - let maybeInstance;
139 -
140 - // Fiber errors if findNodeHandle is called for an umounted component.
141 - // Tests using ReactTestRenderer will trigger this case indirectly.
142 - // Mimicking stack behavior, we should silently ignore this case.
143 - // TODO Fix ReactTestRenderer so we can remove this try/catch.
144 - try {
145 - maybeInstance = findNodeHandle(this);
146 - } catch (error) {}
147 -
148 - // If there is no host component beneath this we should fail silently.
149 - // This is not an error; it could mean a class component rendered null.
150 - if (maybeInstance == null) {
151 - return;
89 + /**
90 + * Measures the on-screen location and dimensions. Even if the React Native
91 + * root view is embedded within another native view, this method will give you
92 + * the absolute coordinates measured from the window. If successful, the
93 + * callback will be called asynchronously with the following arguments:
94 + *
95 + * - x
96 + * - y
97 + * - width
98 + * - height
99 + *
100 + * These values are not available until after natives rendering completes.
101 + */
102 + measureInWindow(callback: MeasureInWindowOnSuccessCallback): void {
103 + UIManager.measureInWindow(
104 + findNodeHandle(this),
105 + mountSafeCallback(this, callback),
106 + );
107 }
108
154 - const viewConfig: ReactNativeBaseComponentViewConfig =
155 - maybeInstance.viewConfig || maybeInstance.canonical.viewConfig;
156 -
157 - const updatePayload = ReactNativeAttributePayload.create(
158 - nativeProps,
159 - viewConfig.validAttributes,
160 - );
161 -
162 - // Avoid the overhead of bridge calls if there's no update.
163 - // This is an expensive no-op for Android, and causes an unnecessary
164 - // view invalidation for certain components (eg RCTTextInput) on iOS.
165 - if (updatePayload != null) {
166 - UIManager.updateView(
167 - maybeInstance._nativeTag,
168 - viewConfig.uiViewClassName,
169 - updatePayload,
109 + /**
110 + * Similar to [`measure()`](#measure), but the resulting location will be
111 + * relative to the supplied ancestor's location.
112 + *
113 + * Obtain a native node handle with `ReactNative.findNodeHandle(component)`.
114 + */
115 + measureLayout(
116 + relativeToNativeNode: number,
117 + onSuccess: MeasureLayoutOnSuccessCallback,
118 + onFail: () => void /* currently unused */,
119 + ): void {
120 + UIManager.measureLayout(
121 + findNodeHandle(this),
122 + relativeToNativeNode,
123 + mountSafeCallback(this, onFail),
124 + mountSafeCallback(this, onSuccess),
125 );
126 }
127 +
128 + /**
129 + * This function sends props straight to native. They will not participate in
130 + * future diff process - this means that if you do not include them in the
131 + * next render, they will remain active (see [Direct
132 + * Manipulation](docs/direct-manipulation.html)).
133 + */
134 + setNativeProps(nativeProps: Object): void {
135 + // Class components don't have viewConfig -> validateAttributes.
136 + // Nor does it make sense to set native props on a non-native component.
137 + // Instead, find the nearest host component and set props on it.
138 + // Use findNodeHandle() rather than ReactNative.findNodeHandle() because
139 + // We want the instance/wrapper (not the native tag).
140 + let maybeInstance;
141 +
142 + // Fiber errors if findNodeHandle is called for an umounted component.
143 + // Tests using ReactTestRenderer will trigger this case indirectly.
144 + // Mimicking stack behavior, we should silently ignore this case.
145 + // TODO Fix ReactTestRenderer so we can remove this try/catch.
146 + try {
147 + maybeInstance = findHostInstance(this);
148 + } catch (error) {}
149 +
150 + // If there is no host component beneath this we should fail silently.
151 + // This is not an error; it could mean a class component rendered null.
152 + if (maybeInstance == null) {
153 + return;
154 + }
155 +
156 + const viewConfig: ReactNativeBaseComponentViewConfig =
157 + maybeInstance.viewConfig || maybeInstance.canonical.viewConfig;
158 +
159 + const updatePayload = ReactNativeAttributePayload.create(
160 + nativeProps,
161 + viewConfig.validAttributes,
162 + );
163 +
164 + // Avoid the overhead of bridge calls if there's no update.
165 + // This is an expensive no-op for Android, and causes an unnecessary
166 + // view invalidation for certain components (eg RCTTextInput) on iOS.
167 + if (updatePayload != null) {
168 + UIManager.updateView(
169 + maybeInstance._nativeTag,
170 + viewConfig.uiViewClassName,
171 + updatePayload,
172 + );
173 + }
174 + }
175 }
173 -}
176
175 -// eslint-disable-next-line no-unused-expressions
176 -(ReactNativeComponent.prototype: NativeMethodsMixinType);
177 + // eslint-disable-next-line no-unused-expressions
178 + (ReactNativeComponent.prototype: NativeMethodsMixinType);
179
178 -export default ReactNativeComponent;
180 + return ReactNativeComponent;
181 +}
packages/react-native-renderer/src/ReactNativeRenderer.js
+49 -6
@@ -25,10 +25,53 @@ import ReactNativeComponent from './ReactNativeComponent';
25 import * as ReactNativeComponentTree from './ReactNativeComponentTree';
26 import ReactNativeFiberRenderer from './ReactNativeFiberRenderer';
27 import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
28 -import {injectFindHostInstance} from './findNodeHandle';
29 -import findNumericNodeHandle from './findNumericNodeHandle';
28
31 -injectFindHostInstance(ReactNativeFiberRenderer.findHostInstance);
29 +import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
30 +import getComponentName from 'shared/getComponentName';
31 +import warning from 'fbjs/lib/warning';
32 +
33 +const findHostInstance = ReactNativeFiberRenderer.findHostInstance;
34 +
35 +function findNodeHandle(componentOrHandle: any): ?number {
36 + if (__DEV__) {
37 + const owner = ReactCurrentOwner.current;
38 + if (owner !== null && owner.stateNode !== null) {
39 + warning(
40 + owner.stateNode._warnedAboutRefsInRender,
41 + '%s is accessing findNodeHandle inside its render(). ' +
42 + 'render() should be a pure function of props and state. It should ' +
43 + 'never access something that requires stale data from the previous ' +
44 + 'render, such as refs. Move this logic to componentDidMount and ' +
45 + 'componentDidUpdate instead.',
46 + getComponentName(owner) || 'A component',
47 + );
48 +
49 + owner.stateNode._warnedAboutRefsInRender = true;
50 + }
51 + }
52 + if (componentOrHandle == null) {
53 + return null;
54 + }
55 + if (typeof componentOrHandle === 'number') {
56 + // Already a node handle
57 + return componentOrHandle;
58 + }
59 + if (componentOrHandle._nativeTag) {
60 + return componentOrHandle._nativeTag;
61 + }
62 + if (componentOrHandle.canonical && componentOrHandle.canonical._nativeTag) {
63 + return componentOrHandle.canonical._nativeTag;
64 + }
65 + const hostInstance = findHostInstance(componentOrHandle);
66 + if (hostInstance == null) {
67 + return hostInstance;
68 + }
69 + if (hostInstance.canonical) {
70 + // Fabric
71 + return hostInstance.canonical._nativeTag;
72 + }
73 + return hostInstance._nativeTag;
74 +}
75
76 ReactGenericBatching.injection.injectRenderer(ReactNativeFiberRenderer);
77
@@ -43,9 +86,9 @@ function computeComponentStackForErrorReporting(reactTag: number): string {
86 const roots = new Map();
87
88 const ReactNativeRenderer: ReactNativeType = {
46 - NativeComponent: ReactNativeComponent,
89 + NativeComponent: ReactNativeComponent(findNodeHandle, findHostInstance),
90
48 - findNodeHandle: findNumericNodeHandle,
91 + findNodeHandle,
92
93 render(element: React$Element<any>, containerTag: any, callback: ?Function) {
94 let root = roots.get(containerTag);
@@ -94,7 +137,7 @@ const ReactNativeRenderer: ReactNativeType = {
137
138 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
139 // Used as a mixin in many createClass-based components
97 - NativeMethodsMixin,
140 + NativeMethodsMixin: NativeMethodsMixin(findNodeHandle, findHostInstance),
141 // Used by react-native-github/Libraries/ components
142 ReactNativeComponentTree, // ScrollResponder
143 computeComponentStackForErrorReporting,
packages/react-native-renderer/src/findNodeHandle.js deleted
-104
@@ -1,104 +0,0 @@
1 -/**
2 - * Copyright (c) 2015-present, Facebook, Inc.
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 * as ReactInstanceMap from 'shared/ReactInstanceMap';
13 -import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
14 -import getComponentName from 'shared/getComponentName';
15 -import invariant from 'fbjs/lib/invariant';
16 -import warning from 'fbjs/lib/warning';
17 -
18 -let findHostInstance = function(fiber: Fiber): any {
19 - return null;
20 -};
21 -
22 -export function injectFindHostInstance(impl: (fiber: Fiber) => any) {
23 - findHostInstance = impl;
24 -}
25 -
26 -/**
27 - * ReactNative vs ReactWeb
28 - * -----------------------
29 - * React treats some pieces of data opaquely. This means that the information
30 - * is first class (it can be passed around), but cannot be inspected. This
31 - * allows us to build infrastructure that reasons about resources, without
32 - * making assumptions about the nature of those resources, and this allows that
33 - * infra to be shared across multiple platforms, where the resources are very
34 - * different. General infra (such as `ReactMultiChild`) reasons opaquely about
35 - * the data, but platform specific code (such as `ReactNativeBaseComponent`) can
36 - * make assumptions about the data.
37 - *
38 - *
39 - * `rootNodeID`, uniquely identifies a position in the generated native view
40 - * tree. Many layers of composite components (created with `React.createClass`)
41 - * can all share the same `rootNodeID`.
42 - *
43 - * `nodeHandle`: A sufficiently unambiguous way to refer to a lower level
44 - * resource (dom node, native view etc). The `rootNodeID` is sufficient for web
45 - * `nodeHandle`s, because the position in a tree is always enough to uniquely
46 - * identify a DOM node (we never have nodes in some bank outside of the
47 - * document). The same would be true for `ReactNative`, but we must maintain a
48 - * mapping that we can send efficiently serializable
49 - * strings across native boundaries.
50 - *
51 - * Opaque name TodaysWebReact FutureWebWorkerReact ReactNative
52 - * ----------------------------------------------------------------------------
53 - * nodeHandle N/A rootNodeID tag
54 - */
55 -
56 -// TODO (bvaughn) Rename the findNodeHandle module to something more descriptive
57 -// eg findInternalHostInstance. This will reduce the likelihood of someone
58 -// accidentally deep-requiring this version.
59 -function findNodeHandle(componentOrHandle: any): any {
60 - if (__DEV__) {
61 - const owner = ReactCurrentOwner.current;
62 - if (owner !== null && owner.stateNode !== null) {
63 - warning(
64 - owner.stateNode._warnedAboutRefsInRender,
65 - '%s is accessing findNodeHandle inside its render(). ' +
66 - 'render() should be a pure function of props and state. It should ' +
67 - 'never access something that requires stale data from the previous ' +
68 - 'render, such as refs. Move this logic to componentDidMount and ' +
69 - 'componentDidUpdate instead.',
70 - getComponentName(owner) || 'A component',
71 - );
72 -
73 - owner.stateNode._warnedAboutRefsInRender = true;
74 - }
75 - }
76 - if (componentOrHandle == null) {
77 - return null;
78 - }
79 - if (typeof componentOrHandle === 'number') {
80 - // Already a node handle
81 - return componentOrHandle;
82 - }
83 -
84 - const component = componentOrHandle;
85 -
86 - // TODO (sophiebits): Wrap iOS native components in a composite wrapper, then
87 - // ReactInstanceMap.get here will always succeed for mounted components
88 - const internalInstance: Fiber = ReactInstanceMap.get(component);
89 - if (internalInstance) {
90 - return findHostInstance(internalInstance);
91 - } else {
92 - if (component) {
93 - return component;
94 - } else {
95 - invariant(
96 - false,
97 - 'findNodeHandle(...): Unable to find node handle for unmounted ' +
98 - 'component.',
99 - );
100 - }
101 - }
102 -}
103 -
104 -export default findNodeHandle;
packages/react-native-renderer/src/findNumericNodeHandle.js deleted
-28
@@ -1,28 +0,0 @@
1 -/**
2 - * Copyright (c) 2015-present, Facebook, Inc.
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 findNodeHandle from './findNodeHandle';
11 -
12 -/**
13 - * External users of findNodeHandle() expect the host tag number return type.
14 - * The injected findNodeHandle() strategy returns the instance wrapper though.
15 - * See NativeMethodsMixin#setNativeProps for more info on why this is done.
16 - */
17 -export default function findNumericNodeHandleFiber(
18 - componentOrHandle: any,
19 -): ?number {
20 - const instance: any = findNodeHandle(componentOrHandle);
21 - if (instance == null || typeof instance === 'number') {
22 - return instance;
23 - }
24 - if (instance.canonical) {
25 - return instance.canonical._nativeTag;
26 - }
27 - return instance._nativeTag;
28 -}
packages/react-noop-renderer/src/ReactNoop.js
+1 -3
@@ -19,7 +19,6 @@ import type {UpdateQueue} from 'react-reconciler/src/ReactFiberUpdateQueue';
19 import type {ReactNodeList} from 'shared/ReactTypes';
20 import ReactFiberReconciler from 'react-reconciler';
21 import {enablePersistentReconciler} from 'shared/ReactFeatureFlags';
22 -import * as ReactInstanceMap from 'shared/ReactInstanceMap';
22 import * as ReactPortal from 'shared/ReactPortal';
23 import emptyObject from 'fbjs/lib/emptyObject';
24 import expect from 'expect';
@@ -401,8 +400,7 @@ const ReactNoop = {
400 if (typeof component.id === 'number') {
401 return component;
402 }
404 - const inst = ReactInstanceMap.get(component);
405 - return inst ? NoopRenderer.findHostInstance(inst) : null;
403 + return NoopRenderer.findHostInstance(component);
404 },
405
406 flushDeferredPri(timeout: number = Infinity): Array<mixed> {
packages/react-reconciler/src/ReactFiberReconciler.js
+20 -3
@@ -20,6 +20,7 @@ import * as ReactInstanceMap from 'shared/ReactInstanceMap';
20 import {HostComponent} from 'shared/ReactTypeOfWork';
21 import emptyObject from 'fbjs/lib/emptyObject';
22 import getComponentName from 'shared/getComponentName';
23 +import invariant from 'fbjs/lib/invariant';
24 import warning from 'fbjs/lib/warning';
25
26 import {createFiberRoot} from './ReactFiberRoot';
@@ -264,7 +265,7 @@ export type Reconciler<C, I, TI> = {
265 ): React$Component<any, any> | TI | I | null,
266
267 // Use for findDOMNode/findHostNode. Legacy API.
267 - findHostInstance(component: Fiber): I | TI | null,
268 + findHostInstance(component: Object): I | TI | null,
269
270 // Used internally for filtering out portals. Legacy API.
271 findHostInstanceWithNoPortals(component: Fiber): I | TI | null,
@@ -402,7 +403,19 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
403 );
404 }
405
405 - function findHostInstance(fiber: Fiber): PI | null {
406 + function findHostInstance(component: Object): PI | null {
407 + const fiber = ReactInstanceMap.get(component);
408 + if (fiber === undefined) {
409 + if (typeof component.render === 'function') {
410 + invariant(false, 'Unable to find node on an unmounted component.');
411 + } else {
412 + invariant(
413 + false,
414 + 'Argument appears to not be a ReactComponent. Keys: %s',
415 + Object.keys(component),
416 + );
417 + }
418 + }
419 const hostFiber = findCurrentHostFiber(fiber);
420 if (hostFiber === null) {
421 return null;
@@ -508,7 +521,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
521 return ReactFiberDevToolsHook.injectInternals({
522 ...devToolsConfig,
523 findHostInstanceByFiber(fiber: Fiber): I | TI | null {
511 - return findHostInstance(fiber);
524 + const hostFiber = findCurrentHostFiber(fiber);
525 + if (hostFiber === null) {
526 + return null;
527 + }
528 + return hostFiber.stateNode;
529 },
530 findFiberByHostInstance(instance: I | TI): Fiber | null {
531 if (!findFiberByHostInstance) {