@samitouri / QOS-React-2 / commits / 8533c0a168

[Fabric] Add dispatchCommand to React Native renderers (#16085)

* Add dispatchCommand to the public export of the React Native renderers * Fixup invalid check * Prettier * Prettier

Eli White committed Jul 8, 2019 at 13:03 UTC 8533c0a168138d2442334021751b42e530d08bb8
9 files changed +233
packages/react-native-renderer/src/ReactFabric.js
+22
@@ -39,6 +39,8 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
39 import getComponentName from 'shared/getComponentName';
40 import warningWithoutStack from 'shared/warningWithoutStack';
41
42 +const {dispatchCommand: fabricDispatchCommand} = nativeFabricUIManager;
43 +
44 const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
45
46 function findNodeHandle(componentOrHandle: any): ?number {
@@ -116,6 +118,26 @@ const ReactFabric: ReactFabricType = {
118 return;
119 },
120
121 + dispatchCommand(handle: any, command: string, args: Array<any>) {
122 + const invalid =
123 + handle._nativeTag == null || handle._internalInstanceHandle == null;
124 +
125 + if (invalid) {
126 + warningWithoutStack(
127 + !invalid,
128 + "dispatchCommand was called with a ref that isn't a " +
129 + 'native component. Use React.forwardRef to get access to the underlying native component',
130 + );
131 + return;
132 + }
133 +
134 + fabricDispatchCommand(
135 + handle._internalInstanceHandle.stateNode.node,
136 + command,
137 + args,
138 + );
139 + },
140 +
141 render(element: React$Element<any>, containerTag: any, callback: ?Function) {
142 let root = roots.get(containerTag);
143
packages/react-native-renderer/src/ReactNativeRenderer.js
+13
@@ -120,6 +120,19 @@ const ReactNativeRenderer: ReactNativeType = {
120
121 findNodeHandle,
122
123 + dispatchCommand(handle: any, command: string, args: Array<any>) {
124 + if (handle._nativeTag == null) {
125 + warningWithoutStack(
126 + handle._nativeTag != null,
127 + "dispatchCommand was called with a ref that isn't a " +
128 + 'native component. Use React.forwardRef to get access to the underlying native component',
129 + );
130 + return;
131 + }
132 +
133 + UIManager.dispatchViewManagerCommand(handle._nativeTag, command, args);
134 + },
135 +
136 setNativeProps,
137
138 render(element: React$Element<any>, containerTag: any, callback: ?Function) {
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 + dispatchCommand(handle: any, command: string, args: Array<any>): void,
135 setNativeProps(handle: any, nativeProps: Object): void,
136 render(
137 element: React$Element<any>,
@@ -147,6 +148,7 @@ export type ReactNativeType = {
148 export type ReactFabricType = {
149 NativeComponent: typeof ReactNativeComponent,
150 findNodeHandle(componentOrHandle: any): ?number,
151 + dispatchCommand(handle: any, command: string, args: Array<any>): void,
152 setNativeProps(handle: any, nativeProps: Object): void,
153 render(
154 element: React$Element<any>,
packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager.js
+2
@@ -120,6 +120,8 @@ const RCTFabricUIManager = {
120 roots.set(rootTag, newChildSet);
121 }),
122
123 + dispatchCommand: jest.fn(),
124 +
125 registerEventHandler: jest.fn(function registerEventHandler(callback) {}),
126
127 measure: jest.fn(function measure(node, callback) {
packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/UIManager.js
+1
@@ -88,6 +88,7 @@ const RCTUIManager = {
88 viewName: viewName,
89 });
90 }),
91 + dispatchViewManagerCommand: jest.fn(),
92 setJSResponder: jest.fn(),
93 setChildren: jest.fn(function setChildren(parentTag, reactTags) {
94 autoCreateRoot(parentTag);
packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
+83
@@ -22,6 +22,10 @@ let NativeMethodsMixin;
22 const SET_NATIVE_PROPS_NOT_SUPPORTED_MESSAGE =
23 'Warning: setNativeProps is not currently supported in Fabric';
24
25 +const DISPATCH_COMMAND_REQUIRES_HOST_COMPONENT =
26 + "Warning: dispatchCommand was called with a ref that isn't a " +
27 + 'native component. Use React.forwardRef to get access to the underlying native component';
28 +
29 jest.mock('shared/ReactFeatureFlags', () =>
30 require('shared/forks/ReactFeatureFlags.native-oss'),
31 );
@@ -255,6 +259,85 @@ describe('ReactFabric', () => {
259 });
260 });
261
262 + it('should call dispatchCommand for native refs', () => {
263 + const View = createReactNativeComponentClass('RCTView', () => ({
264 + validAttributes: {foo: true},
265 + uiViewClassName: 'RCTView',
266 + }));
267 +
268 + [View].forEach(Component => {
269 + nativeFabricUIManager.dispatchCommand.mockClear();
270 +
271 + let viewRef;
272 + ReactFabric.render(
273 + <Component
274 + ref={ref => {
275 + viewRef = ref;
276 + }}
277 + />,
278 + 11,
279 + );
280 +
281 + expect(nativeFabricUIManager.dispatchCommand).not.toBeCalled();
282 + ReactFabric.dispatchCommand(viewRef, 'updateCommand', [10, 20]);
283 + expect(nativeFabricUIManager.dispatchCommand).toHaveBeenCalledTimes(1);
284 + expect(nativeFabricUIManager.dispatchCommand).toHaveBeenCalledWith(
285 + expect.any(Object),
286 + 'updateCommand',
287 + [10, 20],
288 + );
289 + });
290 + });
291 +
292 + it('should warn and no-op if calling dispatchCommand on non native refs', () => {
293 + const View = createReactNativeComponentClass('RCTView', () => ({
294 + validAttributes: {foo: true},
295 + uiViewClassName: 'RCTView',
296 + }));
297 +
298 + class BasicClass extends React.Component {
299 + render() {
300 + return <React.Fragment />;
301 + }
302 + }
303 +
304 + class Subclass extends ReactFabric.NativeComponent {
305 + render() {
306 + return <View />;
307 + }
308 + }
309 +
310 + const CreateClass = createReactClass({
311 + mixins: [NativeMethodsMixin],
312 + render: () => {
313 + return <View />;
314 + },
315 + });
316 +
317 + [BasicClass, Subclass, CreateClass].forEach(Component => {
318 + nativeFabricUIManager.dispatchCommand.mockReset();
319 +
320 + let viewRef;
321 + ReactFabric.render(
322 + <Component
323 + ref={ref => {
324 + viewRef = ref;
325 + }}
326 + />,
327 + 11,
328 + );
329 +
330 + expect(nativeFabricUIManager.dispatchCommand).not.toBeCalled();
331 + expect(() => {
332 + ReactFabric.dispatchCommand(viewRef, 'updateCommand', [10, 20]);
333 + }).toWarnDev([DISPATCH_COMMAND_REQUIRES_HOST_COMPONENT], {
334 + withoutStack: true,
335 + });
336 +
337 + expect(nativeFabricUIManager.dispatchCommand).not.toBeCalled();
338 + });
339 + });
340 +
341 it('setNativeProps on native refs should no-op', () => {
342 const View = createReactNativeComponentClass('RCTView', () => ({
343 validAttributes: {foo: true},
packages/react-native-renderer/src/__tests__/ReactFabricAndNative-test.internal.js
+20
@@ -54,6 +54,26 @@ describe('ReactFabric', () => {
54 expect(handle).toBe(2);
55 });
56
57 + it('dispatches commands on Fabric nodes with the RN renderer', () => {
58 + UIManager.dispatchViewManagerCommand.mockReset();
59 + const View = createReactNativeComponentClass('RCTView', () => ({
60 + validAttributes: {title: true},
61 + uiViewClassName: 'RCTView',
62 + }));
63 +
64 + let ref = React.createRef();
65 +
66 + ReactFabric.render(<View title="bar" ref={ref} />, 11);
67 + expect(UIManager.dispatchViewManagerCommand).not.toBeCalled();
68 + ReactNative.dispatchCommand(ref.current, 'myCommand', [10, 20]);
69 + expect(UIManager.dispatchViewManagerCommand).toHaveBeenCalledTimes(1);
70 + expect(UIManager.dispatchViewManagerCommand).toHaveBeenCalledWith(
71 + expect.any(Number),
72 + 'myCommand',
73 + [10, 20],
74 + );
75 + });
76 +
77 it('sets native props with setNativeProps on Fabric nodes with the RN renderer', () => {
78 UIManager.updateView.mockReset();
79 const View = createReactNativeComponentClass('RCTView', () => ({
packages/react-native-renderer/src/__tests__/ReactNativeMount-test.internal.js
+83
@@ -19,6 +19,10 @@ let createReactNativeComponentClass;
19 let UIManager;
20 let NativeMethodsMixin;
21
22 +const DISPATCH_COMMAND_REQUIRES_HOST_COMPONENT =
23 + "Warning: dispatchCommand was called with a ref that isn't a " +
24 + 'native component. Use React.forwardRef to get access to the underlying native component';
25 +
26 const SET_NATIVE_PROPS_DEPRECATION_MESSAGE =
27 'Warning: Calling ref.setNativeProps(nativeProps) ' +
28 'is deprecated and will be removed in a future release. ' +
@@ -108,6 +112,85 @@ describe('ReactNative', () => {
112 expect(UIManager.updateView).toHaveBeenCalledTimes(4);
113 });
114
115 + it('should call dispatchCommand for native refs', () => {
116 + const View = createReactNativeComponentClass('RCTView', () => ({
117 + validAttributes: {foo: true},
118 + uiViewClassName: 'RCTView',
119 + }));
120 +
121 + [View].forEach(Component => {
122 + UIManager.dispatchViewManagerCommand.mockClear();
123 +
124 + let viewRef;
125 + ReactNative.render(
126 + <Component
127 + ref={ref => {
128 + viewRef = ref;
129 + }}
130 + />,
131 + 11,
132 + );
133 +
134 + expect(UIManager.dispatchViewManagerCommand).not.toBeCalled();
135 + ReactNative.dispatchCommand(viewRef, 'updateCommand', [10, 20]);
136 + expect(UIManager.dispatchViewManagerCommand).toHaveBeenCalledTimes(1);
137 + expect(UIManager.dispatchViewManagerCommand).toHaveBeenCalledWith(
138 + expect.any(Number),
139 + 'updateCommand',
140 + [10, 20],
141 + );
142 + });
143 + });
144 +
145 + it('should warn and no-op if calling dispatchCommand on non native refs', () => {
146 + const View = createReactNativeComponentClass('RCTView', () => ({
147 + validAttributes: {foo: true},
148 + uiViewClassName: 'RCTView',
149 + }));
150 +
151 + class BasicClass extends React.Component {
152 + render() {
153 + return <React.Fragment />;
154 + }
155 + }
156 +
157 + class Subclass extends ReactNative.NativeComponent {
158 + render() {
159 + return <View />;
160 + }
161 + }
162 +
163 + const CreateClass = createReactClass({
164 + mixins: [NativeMethodsMixin],
165 + render: () => {
166 + return <View />;
167 + },
168 + });
169 +
170 + [BasicClass, Subclass, CreateClass].forEach(Component => {
171 + UIManager.dispatchViewManagerCommand.mockReset();
172 +
173 + let viewRef;
174 + ReactNative.render(
175 + <Component
176 + ref={ref => {
177 + viewRef = ref;
178 + }}
179 + />,
180 + 11,
181 + );
182 +
183 + expect(UIManager.dispatchViewManagerCommand).not.toBeCalled();
184 + expect(() => {
185 + ReactNative.dispatchCommand(viewRef, 'updateCommand', [10, 20]);
186 + }).toWarnDev([DISPATCH_COMMAND_REQUIRES_HOST_COMPONENT], {
187 + withoutStack: true,
188 + });
189 +
190 + expect(UIManager.dispatchViewManagerCommand).not.toBeCalled();
191 + });
192 + });
193 +
194 it('should not call UIManager.updateView from ref.setNativeProps for properties that have not changed', () => {
195 const View = createReactNativeComponentClass('RCTView', () => ({
196 validAttributes: {foo: true},
scripts/flow/react-native-host-hooks.js
+7
@@ -44,6 +44,11 @@ declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'
44 rootTag: number,
45 props: ?Object,
46 ) => void,
47 + dispatchViewManagerCommand: (
48 + reactTag: number,
49 + command: string,
50 + args: Array<any>,
51 + ) => void,
52 manageChildren: (
53 containerTag: number,
54 moveFromIndices: Array<number>,
@@ -120,6 +125,8 @@ declare var nativeFabricUIManager: {
125 ) => void,
126 ) => void,
127
128 + dispatchCommand: (node: Object, command: string, args: Array<any>) => void,
129 +
130 measure: (node: Node, callback: MeasureOnSuccessCallback) => void,
131 measureInWindow: (
132 node: Node,