Przeglądaj źródła

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 5 lat temu
rodzic
commit
8ec7bcd83a
No account linked to committer's email address

+ 2
- 2
lib/src/commands/Commands.ts Wyświetl plik

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
 import { CommandsObserver } from '../events/CommandsObserver';
3
 import { CommandsObserver } from '../events/CommandsObserver';
4
 import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
4
 import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
5
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
5
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider';

+ 22
- 2
lib/src/components/ComponentRegistry.test.tsx Wyświetl plik

5
 import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
5
 import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
6
 import { AppRegistryService } from '../adapters/AppRegistryService';
6
 import { AppRegistryService } from '../adapters/AppRegistryService';
7
 import { ComponentProvider } from 'react-native';
7
 import { ComponentProvider } from 'react-native';
8
+import * as React from 'react';
8
 
9
 
9
 const DummyComponent = () => null;
10
 const DummyComponent = () => null;
10
 
11
 
12
+class MyComponent extends React.Component<any, any> {}
13
+
11
 describe('ComponentRegistry', () => {
14
 describe('ComponentRegistry', () => {
12
   let mockedStore: Store;
15
   let mockedStore: Store;
13
   let mockedComponentEventsObserver: ComponentEventsObserver;
16
   let mockedComponentEventsObserver: ComponentEventsObserver;
14
   let mockedComponentWrapper: ComponentWrapper;
17
   let mockedComponentWrapper: ComponentWrapper;
15
   let mockedAppRegistryService: AppRegistryService;
18
   let mockedAppRegistryService: AppRegistryService;
19
+  let componentWrapper: ComponentWrapper;
20
+  let store: Store;
16
   let uut: ComponentRegistry;
21
   let uut: ComponentRegistry;
17
 
22
 
18
   beforeEach(() => {
23
   beforeEach(() => {
20
     mockedComponentEventsObserver = mock(ComponentEventsObserver);
25
     mockedComponentEventsObserver = mock(ComponentEventsObserver);
21
     mockedComponentWrapper = mock(ComponentWrapper);
26
     mockedComponentWrapper = mock(ComponentWrapper);
22
     mockedAppRegistryService = mock(AppRegistryService);
27
     mockedAppRegistryService = mock(AppRegistryService);
28
+    store = instance(mockedStore);
29
+    componentWrapper = instance(mockedComponentWrapper);
23
 
30
 
24
     uut = new ComponentRegistry(
31
     uut = new ComponentRegistry(
25
-      instance(mockedStore),
32
+      store,
26
       instance(mockedComponentEventsObserver),
33
       instance(mockedComponentEventsObserver),
27
-      instance(mockedComponentWrapper),
34
+      componentWrapper,
28
       instance(mockedAppRegistryService)
35
       instance(mockedAppRegistryService)
29
     );
36
     );
30
   });
37
   });
48
     uut.registerComponent('example.MyComponent.name', generator);
55
     uut.registerComponent('example.MyComponent.name', generator);
49
     expect(generator).toHaveBeenCalledTimes(0);
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 Wyświetl plik

20
     reduxStore?: any
20
     reduxStore?: any
21
   ): ComponentProvider {
21
   ): ComponentProvider {
22
     const NavigationComponent = () => {
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
     this.store.setComponentClassForName(componentName.toString(), NavigationComponent);
39
     this.store.setComponentClassForName(componentName.toString(), NavigationComponent);
34
     this.appRegistryService.registerComponent(componentName.toString(), NavigationComponent);
40
     this.appRegistryService.registerComponent(componentName.toString(), NavigationComponent);

+ 7
- 0
lib/src/components/ComponentWrapper.test.tsx Wyświetl plik

168
     expect(store.getComponentInstance('component1')).toBeTruthy();
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
   describe(`register with redux store`, () => {
178
   describe(`register with redux store`, () => {
172
     class MyReduxComp extends React.Component<any> {
179
     class MyReduxComp extends React.Component<any> {
173
       static options() {
180
       static options() {

+ 2
- 2
lib/src/components/ComponentWrapper.tsx Wyświetl plik

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

+ 14
- 0
lib/src/components/Store.ts Wyświetl plik

1
+import * as React from 'react';
1
 import { ComponentProvider } from 'react-native';
2
 import { ComponentProvider } from 'react-native';
2
 import { IWrappedComponent } from './ComponentWrapper';
3
 import { IWrappedComponent } from './ComponentWrapper';
3
 
4
 
5
   private componentsByName: Record<string, ComponentProvider> = {};
6
   private componentsByName: Record<string, ComponentProvider> = {};
6
   private propsById: Record<string, any> = {};
7
   private propsById: Record<string, any> = {};
7
   private componentsInstancesById: Record<string, IWrappedComponent> = {};
8
   private componentsInstancesById: Record<string, IWrappedComponent> = {};
9
+  private wrappedComponents: Record<string, React.ComponentClass<any>> = {};
8
 
10
 
9
   updateProps(componentId: string, props: any) {
11
   updateProps(componentId: string, props: any) {
10
     this.propsById[componentId] = props;
12
     this.propsById[componentId] = props;
38
   getComponentInstance(id: string): IWrappedComponent {
40
   getComponentInstance(id: string): IWrappedComponent {
39
     return this.componentsInstancesById[id];
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
 }