Quellcode durchsuchen

Fix component generator called multiple times (#6239)

Fix registered component generator function called multiple times. Apparently this hasn't resulted in actual bugs, but it's unpredictable behaviour which can easily lead to bugs and issues.

This commit caches registered components so they are created only once.

Fixes #6232
Guy Carmeli vor 4 Jahren
Ursprung
Commit
8ec7bcd83a
Es ist kein Benutzerkonto mit dieser Commiter-Email verbunden

+ 2
- 2
lib/src/commands/Commands.ts Datei anzeigen

@@ -1,5 +1,5 @@
1
-import cloneDeep from 'lodash/cloneDeep'
2
-import map from 'lodash/map'
1
+import cloneDeep from 'lodash/cloneDeep';
2
+import map from 'lodash/map';
3 3
 import { CommandsObserver } from '../events/CommandsObserver';
4 4
 import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
5 5
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider';

+ 22
- 2
lib/src/components/ComponentRegistry.test.tsx Datei anzeigen

@@ -5,14 +5,19 @@ import { ComponentWrapper } from './ComponentWrapper';
5 5
 import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
6 6
 import { AppRegistryService } from '../adapters/AppRegistryService';
7 7
 import { ComponentProvider } from 'react-native';
8
+import * as React from 'react';
8 9
 
9 10
 const DummyComponent = () => null;
10 11
 
12
+class MyComponent extends React.Component<any, any> {}
13
+
11 14
 describe('ComponentRegistry', () => {
12 15
   let mockedStore: Store;
13 16
   let mockedComponentEventsObserver: ComponentEventsObserver;
14 17
   let mockedComponentWrapper: ComponentWrapper;
15 18
   let mockedAppRegistryService: AppRegistryService;
19
+  let componentWrapper: ComponentWrapper;
20
+  let store: Store;
16 21
   let uut: ComponentRegistry;
17 22
 
18 23
   beforeEach(() => {
@@ -20,11 +25,13 @@ describe('ComponentRegistry', () => {
20 25
     mockedComponentEventsObserver = mock(ComponentEventsObserver);
21 26
     mockedComponentWrapper = mock(ComponentWrapper);
22 27
     mockedAppRegistryService = mock(AppRegistryService);
28
+    store = instance(mockedStore);
29
+    componentWrapper = instance(mockedComponentWrapper);
23 30
 
24 31
     uut = new ComponentRegistry(
25
-      instance(mockedStore),
32
+      store,
26 33
       instance(mockedComponentEventsObserver),
27
-      instance(mockedComponentWrapper),
34
+      componentWrapper,
28 35
       instance(mockedAppRegistryService)
29 36
     );
30 37
   });
@@ -48,4 +55,17 @@ describe('ComponentRegistry', () => {
48 55
     uut.registerComponent('example.MyComponent.name', generator);
49 56
     expect(generator).toHaveBeenCalledTimes(0);
50 57
   });
58
+
59
+  it('should wrap component only once', () => {
60
+    componentWrapper.wrap = jest.fn();
61
+    let wrappedComponent: React.ComponentClass<any>;
62
+    store.hasRegisteredWrappedComponent = jest.fn(() => wrappedComponent !== undefined);
63
+    store.setWrappedComponent = jest.fn(() => wrappedComponent = MyComponent);
64
+
65
+    const generator: ComponentProvider = jest.fn(() => DummyComponent);
66
+    uut.registerComponent('example.MyComponent.name', generator)();
67
+    uut.registerComponent('example.MyComponent.name', generator)();
68
+
69
+    expect(componentWrapper.wrap).toHaveBeenCalledTimes(1);
70
+  });
51 71
 });

+ 15
- 9
lib/src/components/ComponentRegistry.ts Datei anzeigen

@@ -20,15 +20,21 @@ export class ComponentRegistry {
20 20
     reduxStore?: any
21 21
   ): ComponentProvider {
22 22
     const NavigationComponent = () => {
23
-      return this.componentWrapper.wrap(
24
-        componentName.toString(),
25
-        componentProvider,
26
-        this.store,
27
-        this.componentEventsObserver,
28
-        concreteComponentProvider,
29
-        ReduxProvider,
30
-        reduxStore
31
-      );
23
+      if (this.store.hasRegisteredWrappedComponent(componentName)) {
24
+        return this.store.getWrappedComponent(componentName);
25
+      } else {
26
+        const wrappedComponent = this.componentWrapper.wrap(
27
+          componentName.toString(),
28
+          componentProvider,
29
+          this.store,
30
+          this.componentEventsObserver,
31
+          concreteComponentProvider,
32
+          ReduxProvider,
33
+          reduxStore
34
+        );
35
+        this.store.setWrappedComponent(componentName, wrappedComponent);
36
+        return wrappedComponent;
37
+      }
32 38
     };
33 39
     this.store.setComponentClassForName(componentName.toString(), NavigationComponent);
34 40
     this.appRegistryService.registerComponent(componentName.toString(), NavigationComponent);

+ 7
- 0
lib/src/components/ComponentWrapper.test.tsx Datei anzeigen

@@ -168,6 +168,13 @@ describe('ComponentWrapper', () => {
168 168
     expect(store.getComponentInstance('component1')).toBeTruthy();
169 169
   });
170 170
 
171
+  it('Component generator is invoked only once', () => {
172
+    const componentGenerator = jest.fn(() => MyComponent);
173
+    uut.wrap(componentName, componentGenerator, store, componentEventsObserver);
174
+
175
+    expect(componentGenerator.mock.calls.length).toBe(1);
176
+  });
177
+
171 178
   describe(`register with redux store`, () => {
172 179
     class MyReduxComp extends React.Component<any> {
173 180
       static options() {

+ 2
- 2
lib/src/components/ComponentWrapper.tsx Datei anzeigen

@@ -1,6 +1,6 @@
1 1
 import * as React from 'react';
2 2
 import { ComponentProvider } from 'react-native';
3
-import merge from 'lodash/merge'
3
+import merge from 'lodash/merge';
4 4
 import { polyfill } from 'react-lifecycles-compat';
5 5
 import hoistNonReactStatics from 'hoist-non-react-statics';
6 6
 
@@ -68,7 +68,7 @@ export class ComponentWrapper {
68 68
     }
69 69
 
70 70
     polyfill(WrappedComponent);
71
-    hoistNonReactStatics(WrappedComponent, concreteComponentProvider());
71
+    hoistNonReactStatics(WrappedComponent, concreteComponentProvider === OriginalComponentGenerator ? GeneratedComponentClass : concreteComponentProvider());
72 72
     return ReduxProvider ? this.wrapWithRedux(WrappedComponent, ReduxProvider, reduxStore) : WrappedComponent;
73 73
   }
74 74
 

+ 14
- 0
lib/src/components/Store.ts Datei anzeigen

@@ -1,3 +1,4 @@
1
+import * as React from 'react';
1 2
 import { ComponentProvider } from 'react-native';
2 3
 import { IWrappedComponent } from './ComponentWrapper';
3 4
 
@@ -5,6 +6,7 @@ export class Store {
5 6
   private componentsByName: Record<string, ComponentProvider> = {};
6 7
   private propsById: Record<string, any> = {};
7 8
   private componentsInstancesById: Record<string, IWrappedComponent> = {};
9
+  private wrappedComponents: Record<string, React.ComponentClass<any>> = {};
8 10
 
9 11
   updateProps(componentId: string, props: any) {
10 12
     this.propsById[componentId] = props;
@@ -38,4 +40,16 @@ export class Store {
38 40
   getComponentInstance(id: string): IWrappedComponent {
39 41
     return this.componentsInstancesById[id];
40 42
   }
43
+
44
+  setWrappedComponent(componentName: string | number, wrappedComponent: React.ComponentClass<any>): void {
45
+    this.wrappedComponents[componentName] = wrappedComponent;
46
+  }
47
+
48
+  hasRegisteredWrappedComponent(componentName: string | number): boolean {
49
+    return componentName in this.wrappedComponents;
50
+  }
51
+
52
+  getWrappedComponent(componentName: string | number): React.ComponentClass<any> {
53
+    return this.wrappedComponents[componentName];
54
+  }
41 55
 }