Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS migration] Migrate 'useNetwork.js' hook to TypeScript (with createOnyxContext, OnyxProvider, ComposeProviders) #29209

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ module.exports = {
},
{
selector: ['parameter', 'method'],
format: ['camelCase'],
format: ['camelCase', 'PascalCase'],
},
],
'@typescript-eslint/ban-types': [
Expand Down
5 changes: 4 additions & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -422,5 +423,7 @@ type OnyxValues = {
[ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM]: OnyxTypes.Form;
};

type OnyxKeyValue<TOnyxKey extends (OnyxKey | OnyxCollectionKey) & keyof OnyxValues> = OnyxEntry<OnyxValues[TOnyxKey]>;

export default ONYXKEYS;
export type {OnyxKey, OnyxCollectionKey, OnyxValues};
export type {OnyxKey, OnyxCollectionKey, OnyxValues, OnyxKeyValue};
29 changes: 0 additions & 29 deletions src/components/ComposeProviders.js

This file was deleted.

14 changes: 14 additions & 0 deletions src/components/ComposeProviders.tsx
Original file line number Diff line number Diff line change
@@ -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<ComponentType<ChildrenProps>>;
};

function ComposeProviders(props: ComposeProvidersProps): ReactNode {
return props.components.reduceRight((memo, Component) => <Component>{memo}</Component>, props.children);
}

ComposeProviders.displayName = 'ComposeProviders';
export default ComposeProviders;
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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 (
<ComposeProviders
components={[
Expand All @@ -40,7 +39,6 @@ function OnyxProvider(props) {
}

OnyxProvider.displayName = 'OnyxProvider';
OnyxProvider.propTypes = propTypes;

export default OnyxProvider;

Expand Down
58 changes: 0 additions & 58 deletions src/components/createOnyxContext.js

This file was deleted.

81 changes: 81 additions & 0 deletions src/components/createOnyxContext.tsx
Original file line number Diff line number Diff line change
@@ -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<TOnyxKey extends OnyxKeys> = Record<TOnyxKey, OnyxKeyValue<TOnyxKey>>;

type ProviderPropsWithOnyx<TOnyxKey extends OnyxKeys> = ChildrenProps & ProviderOnyxProps<TOnyxKey>;

// withOnyxKey types
type WithOnyxKeyProps<TOnyxKey extends OnyxKeys, TNewOnyxKey extends string, TTransformedValue> = {
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<TOnyxKey>, props: any) => TTransformedValue;
};

type WrapComponentWithConsumer<TNewOnyxKey extends string, TTransformedValue> = <TProps extends Record<TNewOnyxKey, TTransformedValue>, TRef>(
WrappedComponent: ComponentType<TProps & RefAttributes<TRef>>,
) => ForwardRefExoticComponent<PropsWithoutRef<Omit<TProps, TNewOnyxKey>> & RefAttributes<TRef>>;

type WithOnyxKey<TOnyxKey extends OnyxKeys> = <TNewOnyxKey extends string = TOnyxKey, TTransformedValue = OnyxKeyValue<TOnyxKey>>(
props?: WithOnyxKeyProps<TOnyxKey, TNewOnyxKey, TTransformedValue>,
) => WrapComponentWithConsumer<TNewOnyxKey, TTransformedValue>;

// createOnyxContext return type
type CreateOnyxContext<TOnyxKey extends OnyxKeys> = [WithOnyxKey<TOnyxKey>, ComponentType<Omit<ProviderPropsWithOnyx<TOnyxKey>, TOnyxKey>>, React.Context<OnyxKeyValue<TOnyxKey>>];

export default <TOnyxKey extends OnyxKeys>(onyxKeyName: TOnyxKey): CreateOnyxContext<TOnyxKey> => {
const Context = createContext<OnyxKeyValue<TOnyxKey>>(null);
function Provider(props: ProviderPropsWithOnyx<TOnyxKey>): ReactNode {
return <Context.Provider value={props[onyxKeyName]}>{props.children}</Context.Provider>;
}

Provider.displayName = `${Str.UCFirst(onyxKeyName)}Provider`;

const ProviderWithOnyx = withOnyx<ProviderPropsWithOnyx<TOnyxKey>, ProviderOnyxProps<TOnyxKey>>({
[onyxKeyName]: {
key: onyxKeyName,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as Record<TOnyxKey, any>)(Provider);
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved

function withOnyxKey<TNewOnyxKey extends string = TOnyxKey, TTransformedValue = OnyxKeyValue<TOnyxKey>>({
propName,
transformValue,
}: WithOnyxKeyProps<TOnyxKey, TNewOnyxKey, TTransformedValue> = {}) {
return <TProps extends Record<TNewOnyxKey, TTransformedValue>, TRef>(WrappedComponent: ComponentType<TProps & RefAttributes<TRef>>) => {
function Consumer(props: Omit<TProps, TNewOnyxKey>, ref: ForwardedRef<TRef>): ReactNode {
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
return (
<Context.Consumer>
{(value) => {
const propsToPass = {
...props,
[propName ?? onyxKeyName]: transformValue ? transformValue(value, props) : value,
} as TProps;

return (
<WrappedComponent
// eslint-disable-next-line react/jsx-props-no-spreading
{...propsToPass}
ref={ref}
/>
);
}}
</Context.Consumer>
);
}

Consumer.displayName = `with${Str.UCFirst(onyxKeyName)}(${getComponentDisplayName(WrappedComponent)})`;
return forwardRef(Consumer);
};
}

return [withOnyxKey, ProviderWithOnyx, Context];
};
15 changes: 8 additions & 7 deletions src/hooks/useNetwork.js → src/hooks/useNetwork.ts
Original file line number Diff line number Diff line change
@@ -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) ?? {};
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
const prevOfflineStatusRef = useRef(isOffline);
useEffect(() => {
// If we were offline before and now we are not offline then we just reconnected
Expand Down
2 changes: 1 addition & 1 deletion src/libs/getComponentDisplayName.ts
Original file line number Diff line number Diff line change
@@ -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<TProps>(component: ComponentType<TProps>): string {
return component.displayName ?? component.name ?? 'Component';
}
8 changes: 8 additions & 0 deletions src/types/utils/ChildrenProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type {ReactNode} from 'react';

type ChildrenProps = {
/** Rendered child component */
children: ReactNode;
};

export default ChildrenProps;
Loading