Skip to content

Commit

Permalink
Support lazy screen registration (#6354)
Browse files Browse the repository at this point in the history
Support lazy screen registration

Add a hook where apps can register components as they are used, instead of having to register every screen before the app is launched.
  • Loading branch information
yedidyak authored Jul 2, 2020
1 parent 5f6853f commit 81c0f87
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 1 deletion.
19 changes: 19 additions & 0 deletions e2e/LazyRegsitration.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Utils from './Utils';
import TestIDs from '../playground/src/testIDs';

const { elementById } = Utils;

describe('Lazy Registration', () => {
beforeEach(async () => {
await device.relaunchApp();
await elementById(TestIDs.STACK_BTN).tap();
});

it('push and pop lazily registered screen', async () => {
await elementById(TestIDs.PUSH_LAZY_BTN).tap();
await expect(elementById(TestIDs.LAZILY_REGISTERED_SCREEN_HEADER)).toBeVisible();
await elementById(TestIDs.POP_BTN).tap();
await expect(elementById(TestIDs.STACK_SCREEN_HEADER)).toBeVisible();
});

});
4 changes: 4 additions & 0 deletions lib/src/Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export class NavigationRoot {
return this.componentRegistry.registerComponent(componentName, componentProvider, concreteComponentProvider);
}

public setLazyComponentRegistrator(lazyRegistratorFn: (lazyComponentRequest: string | number) => void) {
this.store.setLazyComponentRegistrator(lazyRegistratorFn);
}

/**
* Utility helper function like registerComponent,
* wraps the provided component with a react-redux Provider with the passed redux store
Expand Down
19 changes: 18 additions & 1 deletion lib/src/components/Store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,24 @@ describe('Store', () => {
expect(instance.setProps).toHaveBeenCalledWith(props);
});

it('not throw exeption when set props by id component not found', () => {
it('not throw exception when set props by id component not found', () => {
expect(() => uut.updateProps('component1', { foo: 'bar' })).not.toThrow();
});

it('tries to register components lazily when given a lazy registrator', () => {
const MyLazyComponent = () => class MyComponent extends React.Component {};
const MyEagerComponent = () => class MyComponent extends React.Component {};
uut.setComponentClassForName('eager', MyEagerComponent);
const lazyRegistrator = jest.fn((name) => {
if (name === 'lazy') {
uut.setComponentClassForName(name, MyLazyComponent);
}
});
uut.setLazyComponentRegistrator(lazyRegistrator);

expect(uut.getComponentClassForName('eager')).toEqual(MyEagerComponent);
expect(uut.getComponentClassForName('lazy')).toEqual(MyLazyComponent);
expect(uut.getComponentClassForName('lazy')).toEqual(MyLazyComponent);
expect(lazyRegistrator).toHaveBeenCalledTimes(1);
});
});
8 changes: 8 additions & 0 deletions lib/src/components/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export class Store {
private propsById: Record<string, any> = {};
private componentsInstancesById: Record<string, IWrappedComponent> = {};
private wrappedComponents: Record<string, React.ComponentClass<any>> = {};
private lazyRegistratorFn: ((lazyComponentRequest: string | number) => void) | undefined;

updateProps(componentId: string, props: any) {
this.propsById[componentId] = props;
Expand All @@ -30,6 +31,9 @@ export class Store {
}

getComponentClassForName(componentName: string | number): ComponentProvider | undefined {
if (!this.componentsByName[componentName.toString()] && this.lazyRegistratorFn) {
this.lazyRegistratorFn(componentName);
}
return this.componentsByName[componentName.toString()];
}

Expand All @@ -52,4 +56,8 @@ export class Store {
getWrappedComponent(componentName: string | number): React.ComponentClass<any> {
return this.wrappedComponents[componentName];
}

setLazyComponentRegistrator(lazyRegistratorFn: (lazyComponentRequest: string | number) => void) {
this.lazyRegistratorFn = lazyRegistratorFn;
}
}
36 changes: 36 additions & 0 deletions playground/src/screens/LazilyRegisteredScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import {NavigationComponentProps} from 'react-native-navigation';
import Root from '../components/Root';
import Button from '../components/Button';
import Navigation from '../services/Navigation';
import testIDs from '../testIDs';

class LazilyRegisteredScreen extends React.Component<NavigationComponentProps> {
static options() {
return {
topBar: {
testID: testIDs.LAZILY_REGISTERED_SCREEN_HEADER,
title: {
text: 'Lazily Registered Screen'
}
}
};
}

constructor(props: NavigationComponentProps) {
super(props);
Navigation.events().bindComponent(this);
}

render() {
return (
<Root componentId={this.props.componentId}>
<Button label='Pop' testID={testIDs.POP_BTN} onPress={this.pop} />
</Root>
);
}

pop = () => Navigation.pop(this);
}

export default LazilyRegisteredScreen;
1 change: 1 addition & 0 deletions playground/src/screens/Screens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const Screens = {
ReactTitleView: 'ReactTitleView',
EventsScreen: 'EventsScreen',
EventsOverlay: 'EventsOverlay',
LazilyRegisteredScreen: 'LazilyRegisteredScreen',
SideMenuLeft,
SideMenuCenter,
SideMenuRight,
Expand Down
8 changes: 8 additions & 0 deletions playground/src/screens/StackScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {
PUSH_LIFECYCLE_BTN,
POP_NONE_EXISTENT_SCREEN_BTN,
PUSH_CUSTOM_BACK_BTN,
PUSH_LAZY_BTN,
CUSTOM_BACK_BTN,
SEARCH_BTN,
SET_STACK_ROOT_BTN,
Expand Down Expand Up @@ -45,6 +46,11 @@ export default class StackScreen extends React.Component<NavigationComponentProp
testID={POP_NONE_EXISTENT_SCREEN_BTN}
onPress={this.popNoneExistent}
/>
<Button
label='Push Lazily Registered Screen'
testID={PUSH_LAZY_BTN}
onPress={this.pushLazilyRegistered}
/>
<Button
label="Push Custom Back Button"
testID={PUSH_CUSTOM_BACK_BTN}
Expand All @@ -65,6 +71,8 @@ export default class StackScreen extends React.Component<NavigationComponentProp

pushLifecycleScreen = () => Navigation.push(this, Screens.Lifecycle);

pushLazilyRegistered = () => Navigation.push(this, Screens.LazilyRegisteredScreen);

popNoneExistent = () => Navigation.pop('noneExistentComponentId');

pushCustomBackButton = () =>
Expand Down
8 changes: 8 additions & 0 deletions playground/src/screens/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ function registerScreens() {
'navigation.playground.KeyboardScreen',
() => require('./KeyboardScreen').default
);
Navigation.setLazyComponentRegistrator((componentName) => {
if (componentName === Screens.LazilyRegisteredScreen) {
Navigation.registerComponent(
Screens.LazilyRegisteredScreen,
() => require('./LazilyRegisteredScreen').default
);
}
});
}

export { registerScreens };
2 changes: 2 additions & 0 deletions playground/src/testIDs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const testIDs = {
PUSH_BTN: 'PUSH_BUTTON',
PUSH_DETAILS_BTN: 'PUSH_DETAILS_BUTTON',
PUSH_MASTER_BTN: 'PUSH_MASTER_BUTTON',
PUSH_LAZY_BTN: 'PUSH_LAZY_BTN',
PUSH_SIDE_MENU_BTN: 'PUSH_SIDE_MENU_BTN',
SHOW_STATIC_EVENTS_SCREEN: 'SHOW_STATIC_EVENTS_SCREEN',
POP_BTN: 'POP_BUTTON',
Expand Down Expand Up @@ -165,6 +166,7 @@ const testIDs = {
// Headers
WELCOME_SCREEN_HEADER: `WELCOME_SCREEN_HEADER`,
PUSHED_SCREEN_HEADER: `PUSHED_SCREEN_HEADER`,
LAZILY_REGISTERED_SCREEN_HEADER: `LAZILY_REGISTERED_SCREEN_HEADER`,
OPTIONS_SCREEN_HEADER: `OPTIONS_SCREEN_HEADER`,
MODAL_SCREEN: `MODAL_SCREEN`,
CENTERED_TEXT_HEADER: `CENTERED_TEXT_HEADER`,
Expand Down

0 comments on commit 81c0f87

Please sign in to comment.