diff --git a/packages/compose/README.md b/packages/compose/README.md index 674eb97b5c6b46..734dbee0454059 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -81,25 +81,35 @@ name, returns the enhanced component augmented with a generated displayName. _Parameters_ -- _mapComponentToEnhancedComponent_ `Function`: Function mapping component to enhanced component. +- _mapComponentToEnhancedComponent_ `( OriginalComponent: ComponentType< TProps > ) => ComponentType< Subtract< TProps, TObviatedProps > >`: Function mapping component to enhanced component. - _modifierName_ `string`: Seed name from which to generated display name. _Returns_ -- `WPComponent`: Component class with generated display name assigned. +- `HigherOrderComponent< TObviatedProps >`: Component class with generated display name assigned. # **ifCondition** Higher-order component creator, creating a new component which renders if the given condition is satisfied or with the given optional prop name. +_Usage_ + +```ts +type Props = { foo: string }; +const Component = ( props: Props ) =>
{ props.foo }
; +const ConditionalComponent = ifCondition( ( props: Props ) => props.foo.length !== 0 )( Component ); +; // => null +; // =>
bar
; +``` + _Parameters_ -- _predicate_ `Function`: Function to test condition. +- _predicate_ `( props: TProps ) => boolean`: Function to test condition. _Returns_ -- `Function`: Higher-order component. +- `HigherOrderComponent`: Higher-order component. # **pure** diff --git a/packages/compose/src/higher-order/if-condition/index.js b/packages/compose/src/higher-order/if-condition/index.js deleted file mode 100644 index b0596729b7a362..00000000000000 --- a/packages/compose/src/higher-order/if-condition/index.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Internal dependencies - */ -import createHigherOrderComponent from '../../utils/create-higher-order-component'; - -/** - * Higher-order component creator, creating a new component which renders if - * the given condition is satisfied or with the given optional prop name. - * - * @param {Function} predicate Function to test condition. - * - * @return {Function} Higher-order component. - */ -const ifCondition = ( predicate ) => - createHigherOrderComponent( - ( WrappedComponent ) => ( props ) => { - if ( ! predicate( props ) ) { - return null; - } - - return ; - }, - 'ifCondition' - ); - -export default ifCondition; diff --git a/packages/compose/src/higher-order/if-condition/index.tsx b/packages/compose/src/higher-order/if-condition/index.tsx new file mode 100644 index 00000000000000..478635f6b1ec6c --- /dev/null +++ b/packages/compose/src/higher-order/if-condition/index.tsx @@ -0,0 +1,39 @@ +/** + * Internal dependencies + */ +import createHigherOrderComponent from '../../utils/create-higher-order-component'; +// eslint-disable-next-line no-duplicate-imports +import type { HigherOrderComponent } from '../../utils/create-higher-order-component'; + +/** + * Higher-order component creator, creating a new component which renders if + * the given condition is satisfied or with the given optional prop name. + * + * @example + * ```ts + * type Props = { foo: string }; + * const Component = ( props: Props ) =>
{ props.foo }
; + * const ConditionalComponent = ifCondition( ( props: Props ) => props.foo.length !== 0 )( Component ); + * ; // => null + * ; // =>
bar
; + * ``` + * + * @param predicate Function to test condition. + * + * @return Higher-order component. + */ +const ifCondition = < TProps, >( + predicate: ( props: TProps ) => boolean +): HigherOrderComponent => + createHigherOrderComponent< {}, TProps >( + ( WrappedComponent ) => ( props: TProps ) => { + if ( ! predicate( props ) ) { + return null; + } + + return ; + }, + 'ifCondition' + ); + +export default ifCondition; diff --git a/packages/compose/src/utils/create-higher-order-component/index.js b/packages/compose/src/utils/create-higher-order-component/index.js deleted file mode 100644 index 7366fb0c3e06fe..00000000000000 --- a/packages/compose/src/utils/create-higher-order-component/index.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * External dependencies - */ -import { camelCase, upperFirst } from 'lodash'; - -/** - * Given a function mapping a component to an enhanced component and modifier - * name, returns the enhanced component augmented with a generated displayName. - * - * @param {Function} mapComponentToEnhancedComponent Function mapping component - * to enhanced component. - * @param {string} modifierName Seed name from which to - * generated display name. - * - * @return {WPComponent} Component class with generated display name assigned. - */ -function createHigherOrderComponent( - mapComponentToEnhancedComponent, - modifierName -) { - return ( OriginalComponent ) => { - const EnhancedComponent = mapComponentToEnhancedComponent( - OriginalComponent - ); - const { - displayName = OriginalComponent.name || 'Component', - } = OriginalComponent; - EnhancedComponent.displayName = `${ upperFirst( - camelCase( modifierName ) - ) }(${ displayName })`; - - return EnhancedComponent; - }; -} - -export default createHigherOrderComponent; diff --git a/packages/compose/src/utils/create-higher-order-component/index.ts b/packages/compose/src/utils/create-higher-order-component/index.ts new file mode 100644 index 00000000000000..a55ce84ea4a4c8 --- /dev/null +++ b/packages/compose/src/utils/create-higher-order-component/index.ts @@ -0,0 +1,58 @@ +/** + * External dependencies + */ +import { camelCase, upperFirst } from 'lodash'; +// eslint-disable-next-line no-restricted-imports +import type { ComponentType } from 'react'; +import type { Subtract } from 'utility-types'; + +/** + * Higher order components can cause props to be obviated. For example a HOC that + * injects i18n props will obviate the need for the i18n props to be passed to the component. + * + * If a HOC does not obviate the need for any specific props then we default to `{}` which + * essentially subtracts 0 from the original props of the passed in component. An example + * of this is the `pure` HOC which does not change the API surface of the component but + * simply modifies the internals. + */ +export interface HigherOrderComponent< TObviatedProps extends object = {} > { + < TP extends TObviatedProps >( + OriginalComponent: ComponentType< TP > + ): ComponentType< Subtract< TP, TObviatedProps > >; +} + +/** + * Given a function mapping a component to an enhanced component and modifier + * name, returns the enhanced component augmented with a generated displayName. + * + * @param mapComponentToEnhancedComponent Function mapping component to enhanced component. + * @param modifierName Seed name from which to generated display name. + * + * @return Component class with generated display name assigned. + */ +function createHigherOrderComponent< + TObviatedProps extends object, + TProps extends TObviatedProps +>( + mapComponentToEnhancedComponent: ( + OriginalComponent: ComponentType< TProps > + ) => ComponentType< Subtract< TProps, TObviatedProps > >, + modifierName: string +): HigherOrderComponent< TObviatedProps > { + return ( ( OriginalComponent: ComponentType< TProps > ) => { + const EnhancedComponent = mapComponentToEnhancedComponent( + OriginalComponent + ); + + const { + displayName = OriginalComponent.name || 'Component', + } = OriginalComponent; + + EnhancedComponent.displayName = `${ upperFirst( + camelCase( modifierName ) + ) }(${ displayName })`; + + return EnhancedComponent; + } ) as HigherOrderComponent< TObviatedProps >; +} +export default createHigherOrderComponent; diff --git a/packages/compose/tsconfig.json b/packages/compose/tsconfig.json new file mode 100644 index 00000000000000..63dce7aacabbab --- /dev/null +++ b/packages/compose/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "build-types" + }, + "include": [ + "src/higher-order/if-condition/**/*", + "src/utils/**/*" + ] +}