Skip to content

Commit e88b7f9

Browse files
authored
Merge pull request #4348 from Expensify/marcaaron-onyxContextFactory
Perf: Add context factory helper to speed up chat switching
2 parents f693233 + 5958d48 commit e88b7f9

8 files changed

+172
-60
lines changed

src/App.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import CustomStatusBar from './components/CustomStatusBar';
66
import ErrorBoundary from './components/ErrorBoundary';
77
import Expensify from './Expensify';
88
import {LocaleContextProvider} from './components/withLocalize';
9+
import OnyxProvider from './components/OnyxProvider';
10+
import ComposeProviders from './components/ComposeProviders';
911

1012
LogBox.ignoreLogs([
1113
// Basically it means that if the app goes in the background and back to foreground on Android,
@@ -18,14 +20,18 @@ LogBox.ignoreLogs([
1820
]);
1921

2022
const App = () => (
21-
<SafeAreaProvider>
22-
<LocaleContextProvider>
23-
<CustomStatusBar />
24-
<ErrorBoundary errorMessage="E.cash crash caught by error boundary">
25-
<Expensify />
26-
</ErrorBoundary>
27-
</LocaleContextProvider>
28-
</SafeAreaProvider>
23+
<ComposeProviders
24+
components={[
25+
OnyxProvider,
26+
SafeAreaProvider,
27+
LocaleContextProvider,
28+
]}
29+
>
30+
<CustomStatusBar />
31+
<ErrorBoundary errorMessage="E.cash crash caught by error boundary">
32+
<Expensify />
33+
</ErrorBoundary>
34+
</ComposeProviders>
2935
);
3036

3137
App.displayName = 'App';

src/Expensify.js

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@ import lodashGet from 'lodash/get';
22
import PropTypes from 'prop-types';
33
import React, {PureComponent} from 'react';
44
import {View, AppState} from 'react-native';
5-
import Onyx, {withOnyx} from 'react-native-onyx';
5+
import {withOnyx} from 'react-native-onyx';
66
import _ from 'underscore';
77

88
import BootSplash from './libs/BootSplash';
9-
import listenToStorageEvents from './libs/listenToStorageEvents';
109
import * as ActiveClientManager from './libs/ActiveClientManager';
1110
import ONYXKEYS from './ONYXKEYS';
12-
import CONST from './CONST';
1311
import NavigationRoot from './libs/Navigation/NavigationRoot';
14-
import Log from './libs/Log';
1512
import migrateOnyx from './libs/migrateOnyx';
1613
import styles from './styles/styles';
1714
import PushNotification from './libs/Notification/PushNotification';
@@ -24,33 +21,6 @@ import ROUTES from './ROUTES';
2421
import StartupTimer from './libs/StartupTimer';
2522
import {setRedirectToWorkspaceNewAfterSignIn} from './libs/actions/Session';
2623

27-
// Initialize the store when the app loads for the first time
28-
Onyx.init({
29-
keys: ONYXKEYS,
30-
safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
31-
initialKeyStates: {
32-
33-
// Clear any loading and error messages so they do not appear on app startup
34-
[ONYXKEYS.SESSION]: {loading: false, shouldShowComposeInput: true},
35-
[ONYXKEYS.ACCOUNT]: CONST.DEFAULT_ACCOUNT_DATA,
36-
[ONYXKEYS.NETWORK]: {isOffline: false},
37-
[ONYXKEYS.IOU]: {
38-
loading: false, error: false, creatingIOUTransaction: false, isRetrievingCurrency: false,
39-
},
40-
[ONYXKEYS.IS_SIDEBAR_LOADED]: false,
41-
},
42-
registerStorageEventListener: (onStorageEvent) => {
43-
listenToStorageEvents(onStorageEvent);
44-
},
45-
});
46-
Onyx.registerLogger(({level, message}) => {
47-
if (level === 'alert') {
48-
Log.alert(message, 0, {}, false);
49-
} else {
50-
Log.client(message);
51-
}
52-
});
53-
5424
const propTypes = {
5525
/* Onyx Props */
5626

src/components/ComposeProviders.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
const propTypes = {
5+
/** Provider components go here */
6+
components: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.object, PropTypes.func])).isRequired,
7+
8+
/** Rendered child component */
9+
children: PropTypes.node.isRequired,
10+
};
11+
12+
const ComposeProviders = props => (
13+
<>
14+
{props.components.reduceRight((memo, Component) => (
15+
<Component>{memo}</Component>
16+
), props.children)}
17+
</>
18+
);
19+
20+
ComposeProviders.propTypes = propTypes;
21+
ComposeProviders.displayName = 'ComposeProviders';
22+
export default ComposeProviders;

src/components/OnyxProvider.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from 'react';
2+
import Onyx from 'react-native-onyx';
3+
import PropTypes from 'prop-types';
4+
import ONYXKEYS from '../ONYXKEYS';
5+
import createOnyxContext from './createOnyxContext';
6+
import ComposeProviders from './ComposeProviders';
7+
import CONST from '../CONST';
8+
import Log from '../libs/Log';
9+
import listenToStorageEvents from '../libs/listenToStorageEvents';
10+
11+
// Initialize the store when the app loads for the first time
12+
Onyx.init({
13+
keys: ONYXKEYS,
14+
safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
15+
initialKeyStates: {
16+
17+
// Clear any loading and error messages so they do not appear on app startup
18+
[ONYXKEYS.SESSION]: {loading: false, shouldShowComposeInput: true},
19+
[ONYXKEYS.ACCOUNT]: CONST.DEFAULT_ACCOUNT_DATA,
20+
[ONYXKEYS.NETWORK]: {isOffline: false},
21+
[ONYXKEYS.IOU]: {
22+
loading: false, error: false, creatingIOUTransaction: false, isRetrievingCurrency: false,
23+
},
24+
[ONYXKEYS.IS_SIDEBAR_LOADED]: false,
25+
},
26+
registerStorageEventListener: (onStorageEvent) => {
27+
listenToStorageEvents(onStorageEvent);
28+
},
29+
});
30+
Onyx.registerLogger(({level, message}) => {
31+
if (level === 'alert') {
32+
Log.alert(message, 0, {}, false);
33+
} else {
34+
Log.client(message);
35+
}
36+
});
37+
38+
// Set up any providers for individual keys. This should only be used in cases where many components will subscribe to
39+
// the same key (e.g. FlatList renderItem components)
40+
const [withNetwork, NetworkProvider] = createOnyxContext(ONYXKEYS.NETWORK);
41+
const [withPersonalDetails, PersonalDetailsProvider] = createOnyxContext(ONYXKEYS.PERSONAL_DETAILS);
42+
const [
43+
withReportActionsDrafts,
44+
ReportActionsDraftsProvider,
45+
] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS);
46+
47+
const propTypes = {
48+
/** Rendered child component */
49+
children: PropTypes.node.isRequired,
50+
};
51+
52+
const OnyxProvider = props => (
53+
<ComposeProviders
54+
components={[
55+
NetworkProvider,
56+
PersonalDetailsProvider,
57+
ReportActionsDraftsProvider,
58+
]}
59+
>
60+
{props.children}
61+
</ComposeProviders>
62+
);
63+
64+
OnyxProvider.displayName = 'OnyxProvider';
65+
OnyxProvider.propTypes = propTypes;
66+
67+
export default OnyxProvider;
68+
69+
export {
70+
withNetwork,
71+
withPersonalDetails,
72+
withReportActionsDrafts,
73+
};

src/components/createOnyxContext.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React, {createContext, forwardRef} from 'react';
2+
import PropTypes from 'prop-types';
3+
import {withOnyx} from 'react-native-onyx';
4+
import Str from 'expensify-common/lib/str';
5+
import getComponentDisplayName from '../libs/getComponentDisplayName';
6+
7+
const propTypes = {
8+
/** Rendered child component */
9+
children: PropTypes.node.isRequired,
10+
};
11+
12+
export default (onyxKeyName) => {
13+
const Context = createContext();
14+
const Provider = props => (
15+
<Context.Provider value={props[onyxKeyName]}>
16+
{props.children}
17+
</Context.Provider>
18+
);
19+
20+
Provider.propTypes = propTypes;
21+
Provider.displayName = `${Str.UCFirst(onyxKeyName)}Provider`;
22+
23+
const ProviderWithOnyx = withOnyx({
24+
[onyxKeyName]: {
25+
key: onyxKeyName,
26+
},
27+
})(Provider);
28+
29+
const withOnyxKey = ({propName = onyxKeyName, transformValue = () => {}} = {}) => (WrappedComponent) => {
30+
const Consumer = forwardRef((props, ref) => (
31+
<Context.Consumer>
32+
{(value) => {
33+
const propsToPass = {
34+
...props,
35+
[propName]: transformValue ? transformValue(value, props) : value,
36+
};
37+
return (
38+
// eslint-disable-next-line react/jsx-props-no-spreading
39+
<WrappedComponent {...propsToPass} ref={ref} />
40+
);
41+
}}
42+
</Context.Consumer>
43+
));
44+
45+
Consumer.displayName = `with${Str.UCFirst(onyxKeyName)}(${getComponentDisplayName(WrappedComponent)})`;
46+
return Consumer;
47+
};
48+
49+
return [withOnyxKey, ProviderWithOnyx];
50+
};

src/pages/home/report/ReportActionItem.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import _ from 'underscore';
2+
import lodashGet from 'lodash/get';
23
import React, {Component} from 'react';
34
import {View} from 'react-native';
45
import PropTypes from 'prop-types';
5-
import {withOnyx} from 'react-native-onyx';
66
import CONST from '../../../CONST';
77
import ONYXKEYS from '../../../ONYXKEYS';
88
import ReportActionPropTypes from './ReportActionPropTypes';
@@ -23,6 +23,7 @@ import ControlSelection from '../../../libs/ControlSelection';
2323
import canUseTouchScreen from '../../../libs/canUseTouchscreen';
2424
import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu';
2525
import {isActiveReportAction, showContextMenu} from './ContextMenu/ReportActionContextMenu';
26+
import {withReportActionsDrafts} from '../../../components/OnyxProvider';
2627

2728
const propTypes = {
2829
/** The ID of the report this action is on. */
@@ -185,12 +186,12 @@ ReportActionItem.defaultProps = defaultProps;
185186

186187
export default compose(
187188
withWindowDimensions,
188-
withOnyx({
189-
draftMessage: {
190-
key: ({
191-
reportID,
192-
action,
193-
}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${action.reportActionID}`,
189+
withReportActionsDrafts({
190+
propName: 'draftMessage',
191+
transformValue: (drafts, props) => {
192+
const {reportID, action} = props;
193+
const draftKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${action.reportActionID}`;
194+
return lodashGet(drafts, draftKey, '');
194195
},
195196
}),
196197
)(ReportActionItem);

src/pages/home/report/ReportActionItemMessage.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import React from 'react';
22
import {View} from 'react-native';
33
import PropTypes from 'prop-types';
44
import _ from 'underscore';
5-
import {withOnyx} from 'react-native-onyx';
6-
import ONYXKEYS from '../../../ONYXKEYS';
75
import styles from '../../../styles/styles';
86
import ReportActionItemFragment from './ReportActionItemFragment';
97
import ReportActionPropTypes from './ReportActionPropTypes';
8+
import {withNetwork} from '../../../components/OnyxProvider';
109

1110
const propTypes = {
1211
/** The report action */
@@ -43,8 +42,4 @@ ReportActionItemMessage.propTypes = propTypes;
4342
ReportActionItemMessage.defaultProps = defaultProps;
4443
ReportActionItemMessage.displayName = 'ReportActionItemMessage';
4544

46-
export default withOnyx({
47-
network: {
48-
key: ONYXKEYS.NETWORK,
49-
},
50-
})(ReportActionItemMessage);
45+
export default withNetwork()(ReportActionItemMessage);

src/pages/home/report/ReportActionItemSingle.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React from 'react';
22
import {View, Pressable} from 'react-native';
3-
import {withOnyx} from 'react-native-onyx';
43
import PropTypes from 'prop-types';
54
import _ from 'underscore';
65
import Str from 'expensify-common/lib/str';
@@ -10,12 +9,12 @@ import styles from '../../../styles/styles';
109
import CONST from '../../../CONST';
1110
import ReportActionItemDate from './ReportActionItemDate';
1211
import Avatar from '../../../components/Avatar';
13-
import ONYXKEYS from '../../../ONYXKEYS';
1412
import personalDetailsPropType from '../../personalDetailsPropType';
1513
import compose from '../../../libs/compose';
1614
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
1715
import Navigation from '../../../libs/Navigation/Navigation';
1816
import ROUTES from '../../../ROUTES';
17+
import {withPersonalDetails} from '../../../components/OnyxProvider';
1918

2019
const propTypes = {
2120
/** All the data of the action */
@@ -99,9 +98,5 @@ ReportActionItemSingle.displayName = 'ReportActionItemSingle';
9998

10099
export default compose(
101100
withLocalize,
102-
withOnyx({
103-
personalDetails: {
104-
key: ONYXKEYS.PERSONAL_DETAILS,
105-
},
106-
}),
101+
withPersonalDetails(),
107102
)(ReportActionItemSingle);

0 commit comments

Comments
 (0)