diff --git a/src/App.js b/src/App.js index 3fd37312645ee..ae1b8860f6062 100644 --- a/src/App.js +++ b/src/App.js @@ -5,6 +5,8 @@ import CustomStatusBar from './components/CustomStatusBar'; import ErrorBoundary from './components/ErrorBoundary'; import Expensify from './Expensify'; import {LocaleContextProvider} from './components/withLocalize'; +import OnyxProvider from './components/OnyxProvider'; +import ComposeProviders from './components/ComposeProviders'; LogBox.ignoreLogs([ // Basically it means that if the app goes in the background and back to foreground on Android, @@ -17,14 +19,18 @@ LogBox.ignoreLogs([ ]); const App = () => ( - - - - - - - - + + + + + + ); App.displayName = 'App'; diff --git a/src/Expensify.js b/src/Expensify.js index 53cef50574e63..60134552bde4b 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -2,16 +2,13 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {PureComponent} from 'react'; import {View, AppState} from 'react-native'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import BootSplash from './libs/BootSplash'; -import listenToStorageEvents from './libs/listenToStorageEvents'; import * as ActiveClientManager from './libs/ActiveClientManager'; import ONYXKEYS from './ONYXKEYS'; -import CONST from './CONST'; import NavigationRoot from './libs/Navigation/NavigationRoot'; -import Log from './libs/Log'; import migrateOnyx from './libs/migrateOnyx'; import styles from './styles/styles'; import PushNotification from './libs/Notification/PushNotification'; @@ -24,33 +21,6 @@ import ROUTES from './ROUTES'; import StartupTimer from './libs/StartupTimer'; import {setRedirectToWorkspaceNewAfterSignIn} from './libs/actions/Session'; -// Initialize the store when the app loads for the first time -Onyx.init({ - keys: ONYXKEYS, - safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - initialKeyStates: { - - // Clear any loading and error messages so they do not appear on app startup - [ONYXKEYS.SESSION]: {loading: false, shouldShowComposeInput: true}, - [ONYXKEYS.ACCOUNT]: CONST.DEFAULT_ACCOUNT_DATA, - [ONYXKEYS.NETWORK]: {isOffline: false}, - [ONYXKEYS.IOU]: { - loading: false, error: false, creatingIOUTransaction: false, isRetrievingCurrency: false, - }, - [ONYXKEYS.IS_SIDEBAR_LOADED]: false, - }, - registerStorageEventListener: (onStorageEvent) => { - listenToStorageEvents(onStorageEvent); - }, -}); -Onyx.registerLogger(({level, message}) => { - if (level === 'alert') { - Log.alert(message, 0, {}, false); - } else { - Log.client(message); - } -}); - const propTypes = { /* Onyx Props */ diff --git a/src/components/ComposeProviders.js b/src/components/ComposeProviders.js new file mode 100644 index 0000000000000..7cf9af7348d13 --- /dev/null +++ b/src/components/ComposeProviders.js @@ -0,0 +1,22 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const propTypes = { + /** Provider components go here */ + components: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.object, PropTypes.func])).isRequired, + + /** Rendered child component */ + children: PropTypes.node.isRequired, +}; + +const ComposeProviders = props => ( + <> + {props.components.reduceRight((memo, Component) => ( + {memo} + ), props.children)} + +); + +ComposeProviders.propTypes = propTypes; +ComposeProviders.displayName = 'ComposeProviders'; +export default ComposeProviders; diff --git a/src/components/OnyxProvider.js b/src/components/OnyxProvider.js new file mode 100644 index 0000000000000..f88a5e078e31a --- /dev/null +++ b/src/components/OnyxProvider.js @@ -0,0 +1,73 @@ +import React from 'react'; +import Onyx from 'react-native-onyx'; +import PropTypes from 'prop-types'; +import ONYXKEYS from '../ONYXKEYS'; +import createOnyxContext from './createOnyxContext'; +import ComposeProviders from './ComposeProviders'; +import CONST from '../CONST'; +import Log from '../libs/Log'; +import listenToStorageEvents from '../libs/listenToStorageEvents'; + +// Initialize the store when the app loads for the first time +Onyx.init({ + keys: ONYXKEYS, + safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], + initialKeyStates: { + + // Clear any loading and error messages so they do not appear on app startup + [ONYXKEYS.SESSION]: {loading: false, shouldShowComposeInput: true}, + [ONYXKEYS.ACCOUNT]: CONST.DEFAULT_ACCOUNT_DATA, + [ONYXKEYS.NETWORK]: {isOffline: false}, + [ONYXKEYS.IOU]: { + loading: false, error: false, creatingIOUTransaction: false, isRetrievingCurrency: false, + }, + [ONYXKEYS.IS_SIDEBAR_LOADED]: false, + }, + registerStorageEventListener: (onStorageEvent) => { + listenToStorageEvents(onStorageEvent); + }, +}); +Onyx.registerLogger(({level, message}) => { + if (level === 'alert') { + Log.alert(message, 0, {}, false); + } else { + Log.client(message); + } +}); + +// Set up any providers for individual keys. This should only be used in cases where many components will subscribe to +// the same key (e.g. FlatList renderItem components) +const [withNetwork, NetworkProvider] = createOnyxContext(ONYXKEYS.NETWORK); +const [withPersonalDetails, PersonalDetailsProvider] = createOnyxContext(ONYXKEYS.PERSONAL_DETAILS); +const [ + withReportActionsDrafts, + ReportActionsDraftsProvider, +] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS); + +const propTypes = { + /** Rendered child component */ + children: PropTypes.node.isRequired, +}; + +const OnyxProvider = props => ( + + {props.children} + +); + +OnyxProvider.displayName = 'OnyxProvider'; +OnyxProvider.propTypes = propTypes; + +export default OnyxProvider; + +export { + withNetwork, + withPersonalDetails, + withReportActionsDrafts, +}; diff --git a/src/components/createOnyxContext.js b/src/components/createOnyxContext.js new file mode 100644 index 0000000000000..9304efd4a430b --- /dev/null +++ b/src/components/createOnyxContext.js @@ -0,0 +1,50 @@ +import React, {createContext, forwardRef} from 'react'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import Str from 'expensify-common/lib/str'; +import getComponentDisplayName from '../libs/getComponentDisplayName'; + +const propTypes = { + /** Rendered child component */ + children: PropTypes.node.isRequired, +}; + +export default (onyxKeyName) => { + const Context = createContext(); + const Provider = props => ( + + {props.children} + + ); + + Provider.propTypes = propTypes; + Provider.displayName = `${Str.UCFirst(onyxKeyName)}Provider`; + + const ProviderWithOnyx = withOnyx({ + [onyxKeyName]: { + key: onyxKeyName, + }, + })(Provider); + + const withOnyxKey = ({propName = onyxKeyName, transformValue = () => {}} = {}) => (WrappedComponent) => { + const Consumer = forwardRef((props, ref) => ( + + {(value) => { + const propsToPass = { + ...props, + [propName]: transformValue ? transformValue(value, props) : value, + }; + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ); + }} + + )); + + Consumer.displayName = `with${Str.UCFirst(onyxKeyName)}(${getComponentDisplayName(WrappedComponent)})`; + return Consumer; + }; + + return [withOnyxKey, ProviderWithOnyx]; +}; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 010f9e2998172..641ad7904ebb7 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -1,8 +1,8 @@ import _ from 'underscore'; +import lodashGet from 'lodash/get'; import React, {Component} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; import ReportActionPropTypes from './ReportActionPropTypes'; @@ -23,6 +23,7 @@ import ControlSelection from '../../../libs/ControlSelection'; import canUseTouchScreen from '../../../libs/canUseTouchscreen'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; import {isActiveReportAction, showContextMenu} from './ContextMenu/ReportActionContextMenu'; +import {withReportActionsDrafts} from '../../../components/OnyxProvider'; const propTypes = { /** The ID of the report this action is on. */ @@ -185,12 +186,12 @@ ReportActionItem.defaultProps = defaultProps; export default compose( withWindowDimensions, - withOnyx({ - draftMessage: { - key: ({ - reportID, - action, - }) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${action.reportActionID}`, + withReportActionsDrafts({ + propName: 'draftMessage', + transformValue: (drafts, props) => { + const {reportID, action} = props; + const draftKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${action.reportActionID}`; + return lodashGet(drafts, draftKey, ''); }, }), )(ReportActionItem); diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 7f727f84e33a0..d57e63e0cdad7 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -2,11 +2,10 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; -import {withOnyx} from 'react-native-onyx'; -import ONYXKEYS from '../../../ONYXKEYS'; import styles from '../../../styles/styles'; import ReportActionItemFragment from './ReportActionItemFragment'; import ReportActionPropTypes from './ReportActionPropTypes'; +import {withNetwork} from '../../../components/OnyxProvider'; const propTypes = { /** The report action */ @@ -43,8 +42,4 @@ ReportActionItemMessage.propTypes = propTypes; ReportActionItemMessage.defaultProps = defaultProps; ReportActionItemMessage.displayName = 'ReportActionItemMessage'; -export default withOnyx({ - network: { - key: ONYXKEYS.NETWORK, - }, -})(ReportActionItemMessage); +export default withNetwork()(ReportActionItemMessage); diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 84befd28f4c8d..7f05458a4af6b 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -1,6 +1,5 @@ import React from 'react'; import {View, Pressable} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import _ from 'underscore'; import Str from 'expensify-common/lib/str'; @@ -10,12 +9,12 @@ import styles from '../../../styles/styles'; import CONST from '../../../CONST'; import ReportActionItemDate from './ReportActionItemDate'; import Avatar from '../../../components/Avatar'; -import ONYXKEYS from '../../../ONYXKEYS'; import personalDetailsPropType from '../../personalDetailsPropType'; import compose from '../../../libs/compose'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; +import {withPersonalDetails} from '../../../components/OnyxProvider'; const propTypes = { /** All the data of the action */ @@ -99,9 +98,5 @@ ReportActionItemSingle.displayName = 'ReportActionItemSingle'; export default compose( withLocalize, - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - }), + withPersonalDetails(), )(ReportActionItemSingle);