From 4c77c2ef80850b9f8429c5bf1485a68233669e32 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Fri, 21 Aug 2020 01:13:29 -0700 Subject: [PATCH 1/5] create a functional composition utility --- src/lib/ComposeUtil.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/lib/ComposeUtil.js diff --git a/src/lib/ComposeUtil.js b/src/lib/ComposeUtil.js new file mode 100644 index 0000000000000..f97cf8bb2d6e7 --- /dev/null +++ b/src/lib/ComposeUtil.js @@ -0,0 +1,29 @@ +/** + * This is a utility function taken directly from Redux. (We don't want to add Redux as a dependency) + * It enables functional composition, useful for the chaining/composition of HOCs. + * + * For example, instead of: + * + * export default hoc1(config1, hoc2(config2, hoc3(config3)))(Component); + * + * Use this instead: + * + * export default compose( + * hoc1(config1), + * hoc2(config2), + * hoc3(config3), + * )(Component) + * + * @returns {*|(function(...[*]): *)|(function(*): *)} + */ +export default function compose(...funcs) { + if (funcs.length === 0) { + return arg => arg; + } + + if (funcs.length === 1) { + return funcs[0]; + } + + return funcs.reduce((a, b) => (...args) => a(b(...args))); +} From 707f4538804248d0ddcea9beb8112c426bdba8f4 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Fri, 21 Aug 2020 01:33:53 -0700 Subject: [PATCH 2/5] refactor existing components to use functional composition, rename WithIon to follow hoc naming convention --- src/Expensify.js | 4 +-- src/components/{WithIon.js => withIon.js} | 6 ++-- src/page/HomePage/HeaderView.js | 26 ++++++++------- src/page/HomePage/MainView.js | 20 +++++++----- src/page/HomePage/Report/ReportHistoryView.js | 32 +++++++++++-------- src/page/HomePage/SidebarLink.js | 22 +++++++------ src/page/HomePage/SidebarView.js | 4 +-- src/page/SignInPage.js | 14 +++++--- 8 files changed, 74 insertions(+), 54 deletions(-) rename src/components/{WithIon.js => withIon.js} (98%) diff --git a/src/Expensify.js b/src/Expensify.js index 5277d4a3e7dcb..1222737b2c883 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -9,7 +9,7 @@ import Ion from './lib/Ion'; import * as ActiveClientManager from './lib/ActiveClientManager'; import {verifyAuthToken} from './lib/actions/ActionsSession'; import IONKEYS from './IONKEYS'; -import WithIon from './components/WithIon'; +import withIon from './components/withIon'; import styles from './style/StyleSheet'; import { @@ -99,7 +99,7 @@ class Expensify extends Component { Expensify.propTypes = propTypes; Expensify.defaultProps = defaultProps; -export default WithIon({ +export default withIon({ redirectTo: { key: IONKEYS.APP_REDIRECT_TO, loader: () => { diff --git a/src/components/WithIon.js b/src/components/withIon.js similarity index 98% rename from src/components/WithIon.js rename to src/components/withIon.js index a86408dd9ee27..e43d803718627 100644 --- a/src/components/WithIon.js +++ b/src/components/withIon.js @@ -21,7 +21,7 @@ function getDisplayName(component) { export default function (mapIonToState) { return (WrappedComponent) => { - class WithIon extends React.Component { + class withIon extends React.Component { constructor(props) { super(props); @@ -145,7 +145,7 @@ export default function (mapIonToState) { } } - WithIon.displayName = `WithIon(${getDisplayName(WrappedComponent)})`; - return WithIon; + withIon.displayName = `WithIon(${getDisplayName(WrappedComponent)})`; + return withIon; }; } diff --git a/src/page/HomePage/HeaderView.js b/src/page/HomePage/HeaderView.js index c18e8c3a4593c..9734fc49ab14b 100644 --- a/src/page/HomePage/HeaderView.js +++ b/src/page/HomePage/HeaderView.js @@ -4,9 +4,10 @@ import PropTypes from 'prop-types'; import Text from '../../components/Text'; import styles from '../../style/StyleSheet'; import IONKEYS from '../../IONKEYS'; -import WithIon from '../../components/WithIon'; +import withIon from '../../components/withIon'; import {withRouter} from '../../lib/Router'; import LHNToggle from '../../../assets/images/icon-menu-toggle.png'; +import compose from '../../lib/ComposeUtil'; const propTypes = { // Toggles the hamburger menu open and closed @@ -53,13 +54,16 @@ HeaderView.propTypes = propTypes; HeaderView.displayName = 'HeaderView'; HeaderView.defaultProps = defaultProps; -export default withRouter(WithIon({ - // Map this.props.reportName to the data for a specific report in the store, and bind it to the reportName property - // It uses the data returned from the props path (ie. the reportID) to replace %DATAFROMPROPS% in the key it - // binds to - reportName: { - key: `${IONKEYS.REPORT}_%DATAFROMPROPS%`, - path: 'reportName', - pathForProps: 'match.params.reportID', - }, -})(HeaderView)); +export default compose( + withRouter(), + withIon({ + // Map this.props.reportName to the data for a specific report in the store, and bind it to the reportName property + // It uses the data returned from the props path (ie. the reportID) to replace %DATAFROMPROPS% in the key it + // binds to + reportName: { + key: `${IONKEYS.REPORT}_%DATAFROMPROPS%`, + path: 'reportName', + pathForProps: 'match.params.reportID', + }, + }), +)(HeaderView); diff --git a/src/page/HomePage/MainView.js b/src/page/HomePage/MainView.js index bc47603b5d0b3..346c1c3f123c9 100644 --- a/src/page/HomePage/MainView.js +++ b/src/page/HomePage/MainView.js @@ -3,10 +3,11 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import ReportView from './Report/ReportView'; -import WithIon from '../../components/WithIon'; +import withIon from '../../components/withIon'; import IONKEYS from '../../IONKEYS'; import styles from '../../style/StyleSheet'; import {withRouter} from '../../lib/Router'; +import compose from '../../lib/ComposeUtil'; const propTypes = { // This comes from withRouter @@ -62,10 +63,13 @@ class MainView extends React.Component { MainView.propTypes = propTypes; MainView.defaultProps = defaultProps; -export default withRouter(WithIon({ - reports: { - key: `${IONKEYS.REPORT}_[0-9]+$`, - addAsCollection: true, - collectionID: 'reportID', - }, -})(MainView)); +export default compose( + withRouter(), + withIon({ + reports: { + key: `${IONKEYS.REPORT}_[0-9]+$`, + addAsCollection: true, + collectionID: 'reportID', + }, + }), +)(MainView); diff --git a/src/page/HomePage/Report/ReportHistoryView.js b/src/page/HomePage/Report/ReportHistoryView.js index 3df7d0a4fe0d9..cb4568cf9e74a 100644 --- a/src/page/HomePage/Report/ReportHistoryView.js +++ b/src/page/HomePage/Report/ReportHistoryView.js @@ -6,12 +6,13 @@ import lodashGet from 'lodash.get'; import Text from '../../../components/Text'; import Ion from '../../../lib/Ion'; import {fetchHistory, updateLastReadActionID} from '../../../lib/actions/ActionsReport'; -import WithIon from '../../../components/WithIon'; +import withIon from '../../../components/withIon'; import IONKEYS from '../../../IONKEYS'; import ReportHistoryItem from './ReportHistoryItem'; import styles from '../../../style/StyleSheet'; import {withRouter} from '../../../lib/Router'; import ReportHistoryPropsTypes from './ReportHistoryPropsTypes'; +import compose from '../../../lib/ComposeUtil'; const propTypes = { // The ID of the report actions will be created for @@ -163,16 +164,19 @@ ReportHistoryView.propTypes = propTypes; ReportHistoryView.defaultProps = defaultProps; const key = `${IONKEYS.REPORT_HISTORY}_%DATAFROMPROPS%`; -export default withRouter(WithIon({ - authToken: { - key: IONKEYS.SESSION, - path: 'authToken', - prefillWithKey: IONKEYS.SESSION, - }, - reportHistory: { - key, - loader: fetchHistory, - loaderParams: ['%DATAFROMPROPS%'], - pathForProps: 'reportID', - }, -})(ReportHistoryView)); +export default compose( + withRouter(), + withIon({ + authToken: { + key: IONKEYS.SESSION, + path: 'authToken', + prefillWithKey: IONKEYS.SESSION, + }, + reportHistory: { + key, + loader: fetchHistory, + loaderParams: ['%DATAFROMPROPS%'], + pathForProps: 'reportID', + }, + }), +)(ReportHistoryView); diff --git a/src/page/HomePage/SidebarLink.js b/src/page/HomePage/SidebarLink.js index 7af619445c313..4e8dd4003a060 100644 --- a/src/page/HomePage/SidebarLink.js +++ b/src/page/HomePage/SidebarLink.js @@ -5,8 +5,9 @@ import Text from '../../components/Text'; import {withRouter} from '../../lib/Router'; import IONKEYS from '../../IONKEYS'; import styles from '../../style/StyleSheet'; -import WithIon from '../../components/WithIon'; +import withIon from '../../components/withIon'; import PressableLink from '../../components/PressableLink'; +import compose from '../../lib/ComposeUtil'; const propTypes = { // The ID of the report for this link @@ -57,11 +58,14 @@ SidebarLink.displayName = 'SidebarLink'; SidebarLink.propTypes = propTypes; SidebarLink.defaultProps = defaultProps; -export default withRouter(WithIon({ - isUnread: { - key: `${IONKEYS.REPORT}_%DATAFROMPROPS%`, - path: 'hasUnread', - defaultValue: false, - pathForProps: 'reportID', - } -})(SidebarLink)); +export default compose( + withRouter(), + withIon({ + isUnread: { + key: `${IONKEYS.REPORT}_%DATAFROMPROPS%`, + path: 'hasUnread', + defaultValue: false, + pathForProps: 'reportID', + } + }), +)(SidebarLink); diff --git a/src/page/HomePage/SidebarView.js b/src/page/HomePage/SidebarView.js index ad7a25b7b1c31..530d3a73598c8 100644 --- a/src/page/HomePage/SidebarView.js +++ b/src/page/HomePage/SidebarView.js @@ -10,7 +10,7 @@ import Text from '../../components/Text'; import {signOut} from '../../lib/actions/ActionsSession'; import {fetch as getPersonalDetails} from '../../lib/actions/ActionsPersonalDetails'; import styles, {getSafeAreaMargins} from '../../style/StyleSheet'; -import WithIon from '../../components/WithIon'; +import withIon from '../../components/withIon'; import IONKEYS from '../../IONKEYS'; import {fetchAll} from '../../lib/actions/ActionsReport'; import SidebarLink from './SidebarLink'; @@ -147,7 +147,7 @@ class SidebarView extends React.Component { SidebarView.propTypes = propTypes; SidebarView.defaultProps = defaultProps; -export default WithIon({ +export default withIon({ // Map this.props.userDisplayName to the personal details key in the store and bind it to the displayName property // and load it with data from getPersonalDetails() userDisplayName: { diff --git a/src/page/SignInPage.js b/src/page/SignInPage.js index aafad91009451..b6e39fa0a6acd 100644 --- a/src/page/SignInPage.js +++ b/src/page/SignInPage.js @@ -9,10 +9,11 @@ import { View, } from 'react-native'; import PropTypes from 'prop-types'; +import compose from '../lib/ComposeUtil'; import {withRouter} from '../lib/Router'; import {signIn} from '../lib/actions/ActionsSession'; import IONKEYS from '../IONKEYS'; -import WithIon from '../components/WithIon'; +import withIon from '../components/withIon'; import styles from '../style/StyleSheet'; import logo from '../../assets/images/expensify-logo_reversed.png'; @@ -128,7 +129,10 @@ class App extends Component { App.propTypes = propTypes; App.defaultProps = defaultProps; -export default withRouter(WithIon({ - // Bind this.props.error to the error in the session object - error: {key: IONKEYS.SESSION, path: 'error', defaultValue: null}, -})(App)); +export default compose( + withRouter(), + withIon({ + // Bind this.props.error to the error in the session object + error: {key: IONKEYS.SESSION, path: 'error', defaultValue: null}, + }) +)(App); From d05b4dbbbb47b31993a4da5b549a8a231ed32379 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Fri, 21 Aug 2020 10:40:43 -0700 Subject: [PATCH 3/5] fix composition of withRouter - no config --- src/page/HomePage/HeaderView.js | 2 +- src/page/HomePage/MainView.js | 2 +- src/page/HomePage/Report/ReportHistoryView.js | 2 +- src/page/HomePage/SidebarLink.js | 2 +- src/page/SignInPage.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/page/HomePage/HeaderView.js b/src/page/HomePage/HeaderView.js index 9734fc49ab14b..f88f492aa968c 100644 --- a/src/page/HomePage/HeaderView.js +++ b/src/page/HomePage/HeaderView.js @@ -55,7 +55,7 @@ HeaderView.displayName = 'HeaderView'; HeaderView.defaultProps = defaultProps; export default compose( - withRouter(), + withRouter, withIon({ // Map this.props.reportName to the data for a specific report in the store, and bind it to the reportName property // It uses the data returned from the props path (ie. the reportID) to replace %DATAFROMPROPS% in the key it diff --git a/src/page/HomePage/MainView.js b/src/page/HomePage/MainView.js index 346c1c3f123c9..d57ad269c56e7 100644 --- a/src/page/HomePage/MainView.js +++ b/src/page/HomePage/MainView.js @@ -64,7 +64,7 @@ MainView.propTypes = propTypes; MainView.defaultProps = defaultProps; export default compose( - withRouter(), + withRouter, withIon({ reports: { key: `${IONKEYS.REPORT}_[0-9]+$`, diff --git a/src/page/HomePage/Report/ReportHistoryView.js b/src/page/HomePage/Report/ReportHistoryView.js index cb4568cf9e74a..1a990f0b87bd3 100644 --- a/src/page/HomePage/Report/ReportHistoryView.js +++ b/src/page/HomePage/Report/ReportHistoryView.js @@ -165,7 +165,7 @@ ReportHistoryView.defaultProps = defaultProps; const key = `${IONKEYS.REPORT_HISTORY}_%DATAFROMPROPS%`; export default compose( - withRouter(), + withRouter, withIon({ authToken: { key: IONKEYS.SESSION, diff --git a/src/page/HomePage/SidebarLink.js b/src/page/HomePage/SidebarLink.js index 4e8dd4003a060..e106b8e1caa91 100644 --- a/src/page/HomePage/SidebarLink.js +++ b/src/page/HomePage/SidebarLink.js @@ -59,7 +59,7 @@ SidebarLink.propTypes = propTypes; SidebarLink.defaultProps = defaultProps; export default compose( - withRouter(), + withRouter, withIon({ isUnread: { key: `${IONKEYS.REPORT}_%DATAFROMPROPS%`, diff --git a/src/page/SignInPage.js b/src/page/SignInPage.js index b6e39fa0a6acd..0b560f2a97e3d 100644 --- a/src/page/SignInPage.js +++ b/src/page/SignInPage.js @@ -130,7 +130,7 @@ App.propTypes = propTypes; App.defaultProps = defaultProps; export default compose( - withRouter(), + withRouter, withIon({ // Bind this.props.error to the error in the session object error: {key: IONKEYS.SESSION, path: 'error', defaultValue: null}, From 22a59092c5689a41560dd8d93bb80508946f440f Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Sun, 23 Aug 2020 16:11:34 -0700 Subject: [PATCH 4/5] rename file to match export --- src/lib/{ComposeUtil.js => compose.js} | 0 src/page/HomePage/HeaderView.js | 2 +- src/page/HomePage/MainView.js | 2 +- src/page/HomePage/Report/ReportHistoryView.js | 2 +- src/page/HomePage/SidebarLink.js | 2 +- src/page/SignInPage.js | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename src/lib/{ComposeUtil.js => compose.js} (100%) diff --git a/src/lib/ComposeUtil.js b/src/lib/compose.js similarity index 100% rename from src/lib/ComposeUtil.js rename to src/lib/compose.js diff --git a/src/page/HomePage/HeaderView.js b/src/page/HomePage/HeaderView.js index f88f492aa968c..ebf9256a23c6d 100644 --- a/src/page/HomePage/HeaderView.js +++ b/src/page/HomePage/HeaderView.js @@ -7,7 +7,7 @@ import IONKEYS from '../../IONKEYS'; import withIon from '../../components/withIon'; import {withRouter} from '../../lib/Router'; import LHNToggle from '../../../assets/images/icon-menu-toggle.png'; -import compose from '../../lib/ComposeUtil'; +import compose from '../../lib/compose'; const propTypes = { // Toggles the hamburger menu open and closed diff --git a/src/page/HomePage/MainView.js b/src/page/HomePage/MainView.js index d57ad269c56e7..3e6610914eb92 100644 --- a/src/page/HomePage/MainView.js +++ b/src/page/HomePage/MainView.js @@ -7,7 +7,7 @@ import withIon from '../../components/withIon'; import IONKEYS from '../../IONKEYS'; import styles from '../../style/StyleSheet'; import {withRouter} from '../../lib/Router'; -import compose from '../../lib/ComposeUtil'; +import compose from '../../lib/compose'; const propTypes = { // This comes from withRouter diff --git a/src/page/HomePage/Report/ReportHistoryView.js b/src/page/HomePage/Report/ReportHistoryView.js index 1a990f0b87bd3..a619030695dae 100644 --- a/src/page/HomePage/Report/ReportHistoryView.js +++ b/src/page/HomePage/Report/ReportHistoryView.js @@ -12,7 +12,7 @@ import ReportHistoryItem from './ReportHistoryItem'; import styles from '../../../style/StyleSheet'; import {withRouter} from '../../../lib/Router'; import ReportHistoryPropsTypes from './ReportHistoryPropsTypes'; -import compose from '../../../lib/ComposeUtil'; +import compose from '../../../lib/compose'; const propTypes = { // The ID of the report actions will be created for diff --git a/src/page/HomePage/SidebarLink.js b/src/page/HomePage/SidebarLink.js index e106b8e1caa91..322a38e793516 100644 --- a/src/page/HomePage/SidebarLink.js +++ b/src/page/HomePage/SidebarLink.js @@ -7,7 +7,7 @@ import IONKEYS from '../../IONKEYS'; import styles from '../../style/StyleSheet'; import withIon from '../../components/withIon'; import PressableLink from '../../components/PressableLink'; -import compose from '../../lib/ComposeUtil'; +import compose from '../../lib/compose'; const propTypes = { // The ID of the report for this link diff --git a/src/page/SignInPage.js b/src/page/SignInPage.js index 0b560f2a97e3d..c258e9929bc2e 100644 --- a/src/page/SignInPage.js +++ b/src/page/SignInPage.js @@ -9,7 +9,7 @@ import { View, } from 'react-native'; import PropTypes from 'prop-types'; -import compose from '../lib/ComposeUtil'; +import compose from '../lib/compose'; import {withRouter} from '../lib/Router'; import {signIn} from '../lib/actions/ActionsSession'; import IONKEYS from '../IONKEYS'; From 86f90faa285fd09e5b87070b8a901bf498833d23 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Wed, 26 Aug 2020 10:39:09 -0700 Subject: [PATCH 5/5] fix long line --- src/page/HomePage/HeaderView.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/page/HomePage/HeaderView.js b/src/page/HomePage/HeaderView.js index ebf9256a23c6d..3a74ddf5a6642 100644 --- a/src/page/HomePage/HeaderView.js +++ b/src/page/HomePage/HeaderView.js @@ -57,7 +57,8 @@ HeaderView.defaultProps = defaultProps; export default compose( withRouter, withIon({ - // Map this.props.reportName to the data for a specific report in the store, and bind it to the reportName property + // Map this.props.reportName to the data for a specific report in the store, + // and bind it to the reportName property. // It uses the data returned from the props path (ie. the reportID) to replace %DATAFROMPROPS% in the key it // binds to reportName: {