diff --git a/.eslintrc.js b/.eslintrc.js index 75a74ed371c4..83e9479ce0c4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -116,7 +116,7 @@ module.exports = { }, { selector: ['parameter', 'method'], - format: ['camelCase'], + format: ['camelCase', 'PascalCase'], }, ], '@typescript-eslint/ban-types': [ diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index f7c4a11bc52f..39f2e51b60e0 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1,4 +1,5 @@ import {ValueOf} from 'type-fest'; +import {OnyxEntry} from 'react-native-onyx/lib/types'; import DeepValueOf from './types/utils/DeepValueOf'; import * as OnyxTypes from './types/onyx'; import CONST from './CONST'; @@ -422,5 +423,7 @@ type OnyxValues = { [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM]: OnyxTypes.Form; }; +type OnyxKeyValue = OnyxEntry; + export default ONYXKEYS; -export type {OnyxKey, OnyxCollectionKey, OnyxValues}; +export type {OnyxKey, OnyxCollectionKey, OnyxValues, OnyxKeyValue}; diff --git a/src/components/ComposeProviders.js b/src/components/ComposeProviders.js deleted file mode 100644 index edcc0a917c51..000000000000 --- a/src/components/ComposeProviders.js +++ /dev/null @@ -1,29 +0,0 @@ -import _ from 'underscore'; -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, -}; - -function ComposeProviders(props) { - return ( - <> - {_.reduceRight( - props.components, - (memo, Component) => ( - {memo} - ), - props.children, - )} - - ); -} - -ComposeProviders.propTypes = propTypes; -ComposeProviders.displayName = 'ComposeProviders'; -export default ComposeProviders; diff --git a/src/components/ComposeProviders.tsx b/src/components/ComposeProviders.tsx new file mode 100644 index 000000000000..bff36db25533 --- /dev/null +++ b/src/components/ComposeProviders.tsx @@ -0,0 +1,14 @@ +import React, {ComponentType, ReactNode} from 'react'; +import ChildrenProps from '../types/utils/ChildrenProps'; + +type ComposeProvidersProps = ChildrenProps & { + /** Provider components go here */ + components: Array>; +}; + +function ComposeProviders(props: ComposeProvidersProps): ReactNode { + return props.components.reduceRight((memo, Component) => {memo}, props.children); +} + +ComposeProviders.displayName = 'ComposeProviders'; +export default ComposeProviders; diff --git a/src/components/OnyxProvider.js b/src/components/OnyxProvider.tsx similarity index 91% rename from src/components/OnyxProvider.js rename to src/components/OnyxProvider.tsx index 380328cf8137..3bd4ca52c3be 100644 --- a/src/components/OnyxProvider.js +++ b/src/components/OnyxProvider.tsx @@ -1,12 +1,11 @@ import React from 'react'; -import PropTypes from 'prop-types'; import ONYXKEYS from '../ONYXKEYS'; import createOnyxContext from './createOnyxContext'; import ComposeProviders from './ComposeProviders'; // 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, NetworkContext] = createOnyxContext(ONYXKEYS.NETWORK, {}); +const [withNetwork, NetworkProvider, NetworkContext] = createOnyxContext(ONYXKEYS.NETWORK); const [withPersonalDetails, PersonalDetailsProvider] = createOnyxContext(ONYXKEYS.PERSONAL_DETAILS_LIST); const [withCurrentDate, CurrentDateProvider] = createOnyxContext(ONYXKEYS.CURRENT_DATE); const [withReportActionsDrafts, ReportActionsDraftsProvider] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS); @@ -15,12 +14,12 @@ const [withBetas, BetasProvider, BetasContext] = createOnyxContext(ONYXKEYS.BETA const [withReportCommentDrafts, ReportCommentDraftsProvider] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); const [withPreferredTheme, PreferredThemeProvider, PreferredThemeContext] = createOnyxContext(ONYXKEYS.PREFERRED_THEME); -const propTypes = { +type OnyxProviderProps = { /** Rendered child component */ - children: PropTypes.node.isRequired, + children: React.ReactNode; }; -function OnyxProvider(props) { +function OnyxProvider(props: OnyxProviderProps) { return ( { - const Context = createContext(); - function Provider(props) { - return {props.children}; - } - - Provider.propTypes = propTypes; - Provider.displayName = `${Str.UCFirst(onyxKeyName)}Provider`; - - // eslint-disable-next-line rulesdir/onyx-props-must-have-default - 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, - }; - - if (propsToPass[propName] === undefined && defaultValue) { - propsToPass[propName] = defaultValue; - } - return ( - - ); - }} - - )); - - Consumer.displayName = `with${Str.UCFirst(onyxKeyName)}(${getComponentDisplayName(WrappedComponent)})`; - return Consumer; - }; - - return [withOnyxKey, ProviderWithOnyx, Context]; -}; diff --git a/src/components/createOnyxContext.tsx b/src/components/createOnyxContext.tsx new file mode 100644 index 000000000000..d142e551012f --- /dev/null +++ b/src/components/createOnyxContext.tsx @@ -0,0 +1,81 @@ +import React, {ComponentType, ForwardRefExoticComponent, ForwardedRef, PropsWithoutRef, ReactNode, RefAttributes, createContext, forwardRef} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import Str from 'expensify-common/lib/str'; +import getComponentDisplayName from '../libs/getComponentDisplayName'; +import {OnyxCollectionKey, OnyxKey, OnyxKeyValue, OnyxValues} from '../ONYXKEYS'; +import ChildrenProps from '../types/utils/ChildrenProps'; + +type OnyxKeys = (OnyxKey | OnyxCollectionKey) & keyof OnyxValues; + +// Provider types +type ProviderOnyxProps = Record>; + +type ProviderPropsWithOnyx = ChildrenProps & ProviderOnyxProps; + +// withOnyxKey types +type WithOnyxKeyProps = { + propName?: TOnyxKey | TNewOnyxKey; + // It's not possible to infer the type of props of the wrapped component, so we have to use `any` here + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transformValue?: (value: OnyxKeyValue, props: any) => TTransformedValue; +}; + +type WrapComponentWithConsumer = , TRef>( + WrappedComponent: ComponentType>, +) => ForwardRefExoticComponent> & RefAttributes>; + +type WithOnyxKey = >( + props?: WithOnyxKeyProps, +) => WrapComponentWithConsumer; + +// createOnyxContext return type +type CreateOnyxContext = [WithOnyxKey, ComponentType, TOnyxKey>>, React.Context>]; + +export default (onyxKeyName: TOnyxKey): CreateOnyxContext => { + const Context = createContext>(null); + function Provider(props: ProviderPropsWithOnyx): ReactNode { + return {props.children}; + } + + Provider.displayName = `${Str.UCFirst(onyxKeyName)}Provider`; + + const ProviderWithOnyx = withOnyx, ProviderOnyxProps>({ + [onyxKeyName]: { + key: onyxKeyName, + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as Record)(Provider); + + function withOnyxKey>({ + propName, + transformValue, + }: WithOnyxKeyProps = {}) { + return , TRef>(WrappedComponent: ComponentType>) => { + function Consumer(props: Omit, ref: ForwardedRef): ReactNode { + return ( + + {(value) => { + const propsToPass = { + ...props, + [propName ?? onyxKeyName]: transformValue ? transformValue(value, props) : value, + } as TProps; + + return ( + + ); + }} + + ); + } + + Consumer.displayName = `with${Str.UCFirst(onyxKeyName)}(${getComponentDisplayName(WrappedComponent)})`; + return forwardRef(Consumer); + }; + } + + return [withOnyxKey, ProviderWithOnyx, Context]; +}; diff --git a/src/hooks/useNetwork.js b/src/hooks/useNetwork.ts similarity index 74% rename from src/hooks/useNetwork.js rename to src/hooks/useNetwork.ts index a4e973d0194d..4405dd7126a5 100644 --- a/src/hooks/useNetwork.js +++ b/src/hooks/useNetwork.ts @@ -1,16 +1,17 @@ import {useRef, useContext, useEffect} from 'react'; import {NetworkContext} from '../components/OnyxProvider'; -/** - * @param {Object} [options] - * @param {Function} [options.onReconnect] - * @returns {Object} - */ -export default function useNetwork({onReconnect = () => {}} = {}) { +type UseNetworkProps = { + onReconnect?: () => void; +}; + +type UseNetwork = {isOffline?: boolean}; + +export default function useNetwork({onReconnect = () => {}}: UseNetworkProps = {}): UseNetwork { const callback = useRef(onReconnect); callback.current = onReconnect; - const {isOffline} = useContext(NetworkContext); + const {isOffline} = useContext(NetworkContext) ?? {}; const prevOfflineStatusRef = useRef(isOffline); useEffect(() => { // If we were offline before and now we are not offline then we just reconnected diff --git a/src/libs/getComponentDisplayName.ts b/src/libs/getComponentDisplayName.ts index fd1bbcaea521..0bf52d543a84 100644 --- a/src/libs/getComponentDisplayName.ts +++ b/src/libs/getComponentDisplayName.ts @@ -1,6 +1,6 @@ import {ComponentType} from 'react'; /** Returns the display name of a component */ -export default function getComponentDisplayName(component: ComponentType): string { +export default function getComponentDisplayName(component: ComponentType): string { return component.displayName ?? component.name ?? 'Component'; } diff --git a/src/types/utils/ChildrenProps.ts b/src/types/utils/ChildrenProps.ts new file mode 100644 index 000000000000..896f6ff62006 --- /dev/null +++ b/src/types/utils/ChildrenProps.ts @@ -0,0 +1,8 @@ +import type {ReactNode} from 'react'; + +type ChildrenProps = { + /** Rendered child component */ + children: ReactNode; +}; + +export default ChildrenProps;