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},