diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index f3e928da8cb0..8c7729fa1807 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -47,7 +47,6 @@ - [Forwarding refs](#forwarding-refs) - [Hooks and HOCs](#hooks-and-hocs) - [Stateless components vs Pure Components vs Class based components vs Render Props](#stateless-components-vs-pure-components-vs-class-based-components-vs-render-props---when-to-use-what) - - [Composition](#composition) - [Use Refs Appropriately](#use-refs-appropriately) - [Are we allowed to use [insert brand new React feature]?](#are-we-allowed-to-use-insert-brand-new-react-feature-why-or-why-not) - [React Hooks: Frequently Asked Questions](#react-hooks-frequently-asked-questions) @@ -1075,51 +1074,6 @@ Class components are DEPRECATED. Use function components and React hooks. [https://react.dev/reference/react/Component#migrating-a-component-with-lifecycle-methods-from-a-class-to-a-function](https://react.dev/reference/react/Component#migrating-a-component-with-lifecycle-methods-from-a-class-to-a-function) -### Composition - -Avoid the usage of `compose` function to compose HOCs in TypeScript files. Use nesting instead. - -> Why? `compose` function doesn't work well with TypeScript when dealing with several HOCs being used in a component, many times resulting in wrong types and errors. Instead, nesting can be used to allow a seamless use of multiple HOCs and result in a correct return type of the compoment. Also, you can use [hooks instead of HOCs](#hooks-instead-of-hocs) whenever possible to minimize or even remove the need of HOCs in the component. - -From React's documentation - ->Props and composition give you all the flexibility you need to customize a component’s look and behavior in an explicit and safe way. Remember that components may accept arbitrary props, including primitive values, React elements, or functions. ->If you want to reuse non-UI functionality between components, we suggest extracting it into a separate JavaScript module. The components may import it and use that function, object, or a class, without extending it. - - ```ts - // BAD - export default compose( - withCurrentUserPersonalDetails, - withReportOrNotFound(), - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - }), - )(Component); - - // GOOD - export default withCurrentUserPersonalDetails( - withReportOrNotFound()( - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - })(Component), - ), - ); - - // GOOD - alternative to HOC nesting - const ComponentWithOnyx = withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - })(Component); - const ComponentWithReportOrNotFound = withReportOrNotFound()(ComponentWithOnyx); - export default withCurrentUserPersonalDetails(ComponentWithReportOrNotFound); - ``` - -**Note:** If you find that none of these approaches work for you, please ask an Expensify engineer for guidance via Slack or GitHub. - ### Use Refs Appropriately React's documentation explains refs in [detail](https://reactjs.org/docs/refs-and-the-dom.html). It's important to understand when to use them and how to use them to avoid bugs and hard to maintain code. diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx index eb7d9324d2ab..e0e30d14d2a2 100644 --- a/src/components/LocaleContextProvider.tsx +++ b/src/components/LocaleContextProvider.tsx @@ -2,7 +2,6 @@ import React, {createContext, useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import compose from '@libs/compose'; import DateUtils from '@libs/DateUtils'; import * as LocaleDigitUtils from '@libs/LocaleDigitUtils'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; @@ -125,18 +124,17 @@ function LocaleContextProvider({preferredLocale, currentUserPersonalDetails = {} return {children}; } -const Provider = compose( +const Provider = withCurrentUserPersonalDetails( withOnyx({ preferredLocale: { key: ONYXKEYS.NVP_PREFERRED_LOCALE, selector: (preferredLocale) => preferredLocale, }, - }), - withCurrentUserPersonalDetails, -)(LocaleContextProvider); + })(LocaleContextProvider), +); Provider.displayName = 'withOnyx(LocaleContextProvider)'; -export {Provider as LocaleContextProvider, LocaleContext}; +export {LocaleContext, Provider as LocaleContextProvider}; -export type {LocaleContextProps, Locale}; +export type {Locale, LocaleContextProps}; diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index 8168cef99cd3..06128c0d06b7 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -10,7 +10,6 @@ import {PressableWithoutFeedback} from '@components/Pressable'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as UserLocation from '@libs/actions/UserLocation'; -import compose from '@libs/compose'; import getCurrentPosition from '@libs/getCurrentPosition'; import type {GeolocationErrorCallback} from '@libs/getCurrentPosition/getCurrentPosition.types'; import {GeolocationErrorCode} from '@libs/getCurrentPosition/getCurrentPosition.types'; @@ -265,11 +264,8 @@ const MapView = forwardRef( }, ); -export default compose( - withOnyx({ - userLocation: { - key: ONYXKEYS.USER_LOCATION, - }, - }), - memo, -)(MapView); +export default withOnyx({ + userLocation: { + key: ONYXKEYS.USER_LOCATION, + }, +})(memo(MapView)); diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index ebfbeffd68df..2056b4ce4189 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -4,7 +4,6 @@ import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ApiUtils from '@libs/ApiUtils'; -import compose from '@libs/compose'; import * as Network from '@userActions/Network'; import * as Session from '@userActions/Session'; import * as User from '@userActions/User'; @@ -95,11 +94,10 @@ function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) { TestToolMenu.displayName = 'TestToolMenu'; -export default compose( +export default withNetwork()( withOnyx({ user: { key: ONYXKEYS.USER, }, - }), - withNetwork(), -)(TestToolMenu); + })(TestToolMenu), +); diff --git a/src/libs/compose.ts b/src/libs/compose.ts deleted file mode 100644 index dadc586d0f0d..000000000000 --- a/src/libs/compose.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-types */ -/* eslint-disable import/export */ - -/** - * 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) - */ -export default function compose(): (a: R) => R; - -export default function compose(f: F): F; - -/* two functions */ -export default function compose(f1: (...args: A) => R1, f2: (a: R1) => R2): (...args: A) => R2; - -/* three functions */ -export default function compose(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (...args: A) => R3; - -/* four functions */ -export default function compose(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (...args: A) => R4; - -/* five functions */ -export default function compose( - f1: (...args: A) => R1, - f2: (a: R1) => R2, - f3: (a: R2) => R3, - f4: (a: R3) => R4, - f5: (a: R4) => R5, -): (...args: A) => R5; - -/* six functions */ -export default function compose( - f1: (...args: A) => R1, - f2: (a: R1) => R2, - f3: (a: R2) => R3, - f4: (a: R3) => R4, - f5: (a: R4) => R5, - f6: (a: R5) => R6, -): (...args: A) => R6; - -/* rest */ -export default function compose(f1: (a: unknown) => R, ...funcs: Function[]): (...args: unknown[]) => R; - -export default function compose(...funcs: Function[]): Function { - if (funcs.length === 0) { - // infer the argument type so it is usable in inference down the line - return (arg: T) => arg; - } - - if (funcs.length === 1) { - return funcs[0]; - } - - return funcs.reduce( - (a, b) => - (...args: unknown[]) => - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - a(b(...args)), - ); -} diff --git a/src/pages/home/report/withReportAndReportActionOrNotFound.tsx b/src/pages/home/report/withReportAndReportActionOrNotFound.tsx index 58a865e6a58d..3c431e07311e 100644 --- a/src/pages/home/report/withReportAndReportActionOrNotFound.tsx +++ b/src/pages/home/report/withReportAndReportActionOrNotFound.tsx @@ -7,7 +7,6 @@ import {withOnyx} from 'react-native-onyx'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import withWindowDimensions from '@components/withWindowDimensions'; import type {WindowDimensionsProps} from '@components/withWindowDimensions/types'; -import compose from '@libs/compose'; import getComponentDisplayName from '@libs/getComponentDisplayName'; import type {FlagCommentNavigatorParamList, SplitDetailsNavigatorParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; @@ -103,7 +102,7 @@ export default function , OnyxProps>({ report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, @@ -138,9 +137,8 @@ export default function ; -}; +type WorkspaceRateAndUnitPageBaseProps = WithPolicyProps; type WorkspaceRateAndUnitOnyxProps = { workspaceRateAndUnit: OnyxEntry; @@ -150,7 +145,7 @@ function WorkspaceRateAndUnitPage(props: WorkspaceRateAndUnitPageProps) { WorkspaceRateAndUnitPage.displayName = 'WorkspaceRateAndUnitPage'; -export default compose( +export default withPolicy( withOnyx({ // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM reimbursementAccount: { @@ -159,7 +154,5 @@ export default compose( workspaceRateAndUnit: { key: ONYXKEYS.WORKSPACE_RATE_AND_UNIT, }, - }), - withPolicy, - withNetwork(), -)(WorkspaceRateAndUnitPage); + })(WorkspaceRateAndUnitPage), +); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index ec37d1d110b1..8d3bf6701ccc 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -1,18 +1,17 @@ import React, {useCallback, useEffect, useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapperWithRef from '@components/Form/InputWrapper'; import type {FormOnyxValues} from '@components/Form/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import {validateRateValue} from '@libs/PolicyDistanceRatesUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; -import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import withPolicy from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import * as Policy from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; @@ -99,11 +98,10 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { WorkspaceRatePage.displayName = 'WorkspaceRatePage'; -export default compose( +export default withPolicy( withOnyx({ workspaceRateAndUnit: { key: ONYXKEYS.WORKSPACE_RATE_AND_UNIT, }, - }), - withPolicy, -)(WorkspaceRatePage); + })(WorkspaceRatePage), +); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index e8430cab60bb..5d8c7a0920bd 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -1,16 +1,15 @@ import React, {useEffect, useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import Text from '@components/Text'; import type {UnitItemType} from '@components/UnitPicker'; import UnitPicker from '@components/UnitPicker'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import withPolicy from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import * as Policy from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; @@ -70,12 +69,10 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { } WorkspaceUnitPage.displayName = 'WorkspaceUnitPage'; - -export default compose( +export default withPolicy( withOnyx({ workspaceRateAndUnit: { key: ONYXKEYS.WORKSPACE_RATE_AND_UNIT, }, - }), - withPolicy, -)(WorkspaceUnitPage); + })(WorkspaceUnitPage), +); diff --git a/src/pages/workspace/withPolicyAndFullscreenLoading.tsx b/src/pages/workspace/withPolicyAndFullscreenLoading.tsx index 161320441843..2467136a382b 100644 --- a/src/pages/workspace/withPolicyAndFullscreenLoading.tsx +++ b/src/pages/workspace/withPolicyAndFullscreenLoading.tsx @@ -4,7 +4,6 @@ import React, {forwardRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import compose from '@libs/compose'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList} from '@src/types/onyx'; import type {WithPolicyOnyxProps, WithPolicyProps} from './withPolicy'; @@ -49,7 +48,7 @@ export default function withPolicyAndFullscreenLoading, WithPolicyAndFullscreenLoadingOnyxProps>({ isLoadingReportData: { key: ONYXKEYS.IS_LOADING_REPORT_DATA, @@ -57,9 +56,8 @@ export default function withPolicyAndFullscreenLoading