@samitouri / QOS-React / commits / b0f45c0fc6

Adding ReactNative.setNativeProps that takes a ref (#14907)

* Adding ReactNative.setNativeProps that takes a ref * Adding test for components rendered with Fabric with Paper's setNativeProps * Fixing flow types * Fix prettier * Rename ReactNativeSetNativeProps.js to be more general

Eli White committed Feb 20, 2019 at 23:20 UTC b0f45c0fc6ed690739401be9adccb77a80c4f7ba
7 files changed +242 -1
packages/react-native-renderer/src/ReactFabric.js
+3
@@ -32,6 +32,7 @@ import NativeMethodsMixin from './NativeMethodsMixin';
32 import ReactNativeComponent from './ReactNativeComponent';
33 import {getClosestInstanceFromNode} from './ReactFabricComponentTree';
34 import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
35 +import {setNativeProps} from './ReactNativeRendererSharedExports';
36
37 import ReactSharedInternals from 'shared/ReactSharedInternals';
38 import getComponentName from 'shared/getComponentName';
@@ -104,6 +105,8 @@ const ReactFabric: ReactFabricType = {
105
106 findNodeHandle,
107
108 + setNativeProps,
109 +
110 render(element: React$Element<any>, containerTag: any, callback: ?Function) {
111 let root = roots.get(containerTag);
112
packages/react-native-renderer/src/ReactNativeRenderer.js
+3
@@ -38,6 +38,7 @@ import NativeMethodsMixin from './NativeMethodsMixin';
38 import ReactNativeComponent from './ReactNativeComponent';
39 import {getClosestInstanceFromNode} from './ReactNativeComponentTree';
40 import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
41 +import {setNativeProps} from './ReactNativeRendererSharedExports';
42
43 import ReactSharedInternals from 'shared/ReactSharedInternals';
44 import getComponentName from 'shared/getComponentName';
@@ -116,6 +117,8 @@ const ReactNativeRenderer: ReactNativeType = {
117
118 findNodeHandle,
119
120 + setNativeProps,
121 +
122 render(element: React$Element<any>, containerTag: any, callback: ?Function) {
123 let root = roots.get(containerTag);
124
packages/react-native-renderer/src/ReactNativeRendererSharedExports.js new
+43
@@ -0,0 +1,43 @@
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 {create} from './ReactNativeAttributePayload';
11 +import {warnForStyleProps} from './NativeMethodsMixinUtils';
12 +
13 +import warningWithoutStack from 'shared/warningWithoutStack';
14 +
15 +// Module provided by RN:
16 +import UIManager from 'UIManager';
17 +
18 +export function setNativeProps(handle: any, nativeProps: Object): void {
19 + if (handle._nativeTag == null) {
20 + warningWithoutStack(
21 + handle._nativeTag != null,
22 + "setNativeProps was called on a ref that isn't a " +
23 + 'native component. Use React.forwardRef to get access to the underlying native component',
24 + );
25 + return;
26 + }
27 +
28 + if (__DEV__) {
29 + warnForStyleProps(nativeProps, handle.viewConfig.validAttributes);
30 + }
31 +
32 + const updatePayload = create(nativeProps, handle.viewConfig.validAttributes);
33 + // Avoid the overhead of bridge calls if there's no update.
34 + // This is an expensive no-op for Android, and causes an unnecessary
35 + // view invalidation for certain components (eg RCTTextInput) on iOS.
36 + if (updatePayload != null) {
37 + UIManager.updateView(
38 + handle._nativeTag,
39 + handle.viewConfig.uiViewClassName,
40 + updatePayload,
41 + );
42 + }
43 +}
packages/react-native-renderer/src/ReactNativeTypes.js
+2
@@ -131,6 +131,7 @@ type SecretInternalsFabricType = {
131 export type ReactNativeType = {
132 NativeComponent: typeof ReactNativeComponent,
133 findNodeHandle(componentOrHandle: any): ?number,
134 + setNativeProps(handle: any, nativeProps: Object): void,
135 render(
136 element: React$Element<any>,
137 containerTag: any,
@@ -146,6 +147,7 @@ export type ReactNativeType = {
147 export type ReactFabricType = {
148 NativeComponent: typeof ReactNativeComponent,
149 findNodeHandle(componentOrHandle: any): ?number,
150 + setNativeProps(handle: any, nativeProps: Object): void,
151 render(
152 element: React$Element<any>,
153 containerTag: any,
packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
+85 -1
@@ -167,7 +167,7 @@ describe('ReactFabric', () => {
167 expect(FabricUIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
168 });
169
170 - it('should not call UIManager.updateView from setNativeProps for properties that have not changed', () => {
170 + it('should not call UIManager.updateView from ref.setNativeProps for properties that have not changed', () => {
171 const View = createReactNativeComponentClass('RCTView', () => ({
172 validAttributes: {foo: true},
173 uiViewClassName: 'RCTView',
@@ -214,6 +214,90 @@ describe('ReactFabric', () => {
214 });
215 });
216
217 + it('should be able to setNativeProps on native refs', () => {
218 + const View = createReactNativeComponentClass('RCTView', () => ({
219 + validAttributes: {foo: true},
220 + uiViewClassName: 'RCTView',
221 + }));
222 +
223 + UIManager.updateView.mockReset();
224 +
225 + let viewRef;
226 + ReactFabric.render(
227 + <View
228 + foo="bar"
229 + ref={ref => {
230 + viewRef = ref;
231 + }}
232 + />,
233 + 11,
234 + );
235 +
236 + expect(UIManager.updateView).not.toBeCalled();
237 + ReactFabric.setNativeProps(viewRef, {foo: 'baz'});
238 + expect(UIManager.updateView).toHaveBeenCalledTimes(1);
239 + expect(UIManager.updateView).toHaveBeenCalledWith(
240 + expect.any(Number),
241 + 'RCTView',
242 + {foo: 'baz'},
243 + );
244 + });
245 +
246 + it('should warn and no-op if calling setNativeProps on non native refs', () => {
247 + const View = createReactNativeComponentClass('RCTView', () => ({
248 + validAttributes: {foo: true},
249 + uiViewClassName: 'RCTView',
250 + }));
251 +
252 + class BasicClass extends React.Component {
253 + render() {
254 + return <React.Fragment />;
255 + }
256 + }
257 +
258 + class Subclass extends ReactFabric.NativeComponent {
259 + render() {
260 + return <View />;
261 + }
262 + }
263 +
264 + const CreateClass = createReactClass({
265 + mixins: [NativeMethodsMixin],
266 + render: () => {
267 + return <View />;
268 + },
269 + });
270 +
271 + [BasicClass, Subclass, CreateClass].forEach(Component => {
272 + UIManager.updateView.mockReset();
273 +
274 + let viewRef;
275 + ReactFabric.render(
276 + <Component
277 + foo="bar"
278 + ref={ref => {
279 + viewRef = ref;
280 + }}
281 + />,
282 + 11,
283 + );
284 +
285 + expect(UIManager.updateView).not.toBeCalled();
286 + expect(() => {
287 + ReactFabric.setNativeProps(viewRef, {foo: 'baz'});
288 + }).toWarnDev(
289 + [
290 + "Warning: setNativeProps was called on a ref that isn't a " +
291 + 'native component. Use React.forwardRef to get access ' +
292 + 'to the underlying native component',
293 + ],
294 + {withoutStack: true},
295 + );
296 +
297 + expect(UIManager.updateView).not.toBeCalled();
298 + });
299 + });
300 +
301 it('returns the correct instance and calls it in the callback', () => {
302 const View = createReactNativeComponentClass('RCTView', () => ({
303 validAttributes: {foo: true},
packages/react-native-renderer/src/__tests__/ReactFabricAndNative-test.internal.js
+22
@@ -13,12 +13,14 @@
13 let React;
14 let ReactFabric;
15 let ReactNative;
16 +let UIManager;
17 let createReactNativeComponentClass;
18
19 describe('ReactFabric', () => {
20 beforeEach(() => {
21 jest.resetModules();
22 ReactNative = require('react-native-renderer');
23 + UIManager = require('UIManager');
24 jest.resetModules();
25 jest.mock('shared/ReactFeatureFlags', () =>
26 require('shared/forks/ReactFeatureFlags.native-oss'),
@@ -49,4 +51,24 @@ describe('ReactFabric', () => {
51 let handle = ReactNative.findNodeHandle(ref.current);
52 expect(handle).toBe(2);
53 });
54 +
55 + it('sets native props with setNativeProps on Fabric nodes with the RN renderer', () => {
56 + UIManager.updateView.mockReset();
57 + const View = createReactNativeComponentClass('RCTView', () => ({
58 + validAttributes: {title: true},
59 + uiViewClassName: 'RCTView',
60 + }));
61 +
62 + let ref = React.createRef();
63 +
64 + ReactFabric.render(<View title="bar" ref={ref} />, 11);
65 + expect(UIManager.updateView).not.toBeCalled();
66 + ReactNative.setNativeProps(ref.current, {title: 'baz'});
67 + expect(UIManager.updateView).toHaveBeenCalledTimes(1);
68 + expect(UIManager.updateView).toHaveBeenCalledWith(
69 + expect.any(Number),
70 + 'RCTView',
71 + {title: 'baz'},
72 + );
73 + });
74 });
packages/react-native-renderer/src/__tests__/ReactNativeMount-test.internal.js
+84
@@ -145,6 +145,90 @@ describe('ReactNative', () => {
145 });
146 });
147
148 + it('should be able to setNativeProps on native refs', () => {
149 + const View = createReactNativeComponentClass('RCTView', () => ({
150 + validAttributes: {foo: true},
151 + uiViewClassName: 'RCTView',
152 + }));
153 +
154 + UIManager.updateView.mockReset();
155 +
156 + let viewRef;
157 + ReactNative.render(
158 + <View
159 + foo="bar"
160 + ref={ref => {
161 + viewRef = ref;
162 + }}
163 + />,
164 + 11,
165 + );
166 +
167 + expect(UIManager.updateView).not.toBeCalled();
168 + ReactNative.setNativeProps(viewRef, {foo: 'baz'});
169 + expect(UIManager.updateView).toHaveBeenCalledTimes(1);
170 + expect(UIManager.updateView).toHaveBeenCalledWith(
171 + expect.any(Number),
172 + 'RCTView',
173 + {foo: 'baz'},
174 + );
175 + });
176 +
177 + it('should warn and no-op if calling setNativeProps on non native refs', () => {
178 + const View = createReactNativeComponentClass('RCTView', () => ({
179 + validAttributes: {foo: true},
180 + uiViewClassName: 'RCTView',
181 + }));
182 +
183 + class BasicClass extends React.Component {
184 + render() {
185 + return <React.Fragment />;
186 + }
187 + }
188 +
189 + class Subclass extends ReactNative.NativeComponent {
190 + render() {
191 + return <View />;
192 + }
193 + }
194 +
195 + const CreateClass = createReactClass({
196 + mixins: [NativeMethodsMixin],
197 + render: () => {
198 + return <View />;
199 + },
200 + });
201 +
202 + [BasicClass, Subclass, CreateClass].forEach(Component => {
203 + UIManager.updateView.mockReset();
204 +
205 + let viewRef;
206 + ReactNative.render(
207 + <Component
208 + foo="bar"
209 + ref={ref => {
210 + viewRef = ref;
211 + }}
212 + />,
213 + 11,
214 + );
215 +
216 + expect(UIManager.updateView).not.toBeCalled();
217 + expect(() => {
218 + ReactNative.setNativeProps(viewRef, {foo: 'baz'});
219 + }).toWarnDev(
220 + [
221 + "Warning: setNativeProps was called on a ref that isn't a " +
222 + 'native component. Use React.forwardRef to get access ' +
223 + 'to the underlying native component',
224 + ],
225 + {withoutStack: true},
226 + );
227 +
228 + expect(UIManager.updateView).not.toBeCalled();
229 + });
230 + });
231 +
232 it('returns the correct instance and calls it in the callback', () => {
233 const View = createReactNativeComponentClass('RCTView', () => ({
234 validAttributes: {foo: true},