diff --git a/types/recompose/index.d.ts b/types/recompose/index.d.ts index 09c8d6a59d..3e006c175d 100644 --- a/types/recompose/index.d.ts +++ b/types/recompose/index.d.ts @@ -2,21 +2,25 @@ // Project: https://github.com/acdlite/recompose // Definitions by: Iskander Sierra // Samuel DeSota +// Curtis Layne // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.3 +// TypeScript Version: 2.4 /// declare module 'recompose' { import * as React from 'react'; - import { ComponentClass, StatelessComponent, ValidationMap } from 'react'; + import { ComponentType as Component, ComponentClass, StatelessComponent, ValidationMap } from 'react'; - type Component

= ComponentClass

| StatelessComponent

; type mapper = (input: TInner) => TOutter; type predicate = mapper; type predicateDiff = (current: T, next: T) => boolean + // Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766 + type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]; + type Omit = Pick>; + interface Observer{ next(props: T): void; complete(): void; @@ -33,43 +37,59 @@ declare module 'recompose' { interface ComponentEnhancer { (component: Component): ComponentClass; } - export interface InferableComponentEnhancer { - )>(component: TComp): TComp; + + // Injects props and removes them from the prop requirements. + // Will not pass through the injected props if they are passed in during + // render. Also adds new prop requirements from TNeedsProps. + export interface InferableComponentEnhancerWithProps { +

( + component: Component

+ ): React.ComponentType & TNeedsProps> } + // Injects props and removes them from the prop requirements. + // Will not pass through the injected props if they are passed in during + // render. + export type InferableComponentEnhancer = + InferableComponentEnhancerWithProps + + // Injects default props and makes them optional. Will still pass through + // the injected props if they are passed in during render. + export type DefaultingInferableComponentEnhancer = + InferableComponentEnhancerWithProps> // Higher-order components: https://github.com/acdlite/recompose/blob/master/docs/API.md#higher-order-components // mapProps: https://github.com/acdlite/recompose/blob/master/docs/API.md#mapprops export function mapProps( - propsMapper: mapper - ): ComponentEnhancer; + propsMapper: mapper, + ): InferableComponentEnhancerWithProps; // withProps: https://github.com/acdlite/recompose/blob/master/docs/API.md#withprops export function withProps( createProps: TInner | mapper - ): ComponentEnhancer; + ): InferableComponentEnhancerWithProps; // withPropsOnChange: https://github.com/acdlite/recompose/blob/master/docs/API.md#withpropsonchange export function withPropsOnChange( shouldMapOrKeys: string[] | predicateDiff, createProps: mapper - ): ComponentEnhancer; + ): InferableComponentEnhancerWithProps; // withHandlers: https://github.com/acdlite/recompose/blob/master/docs/API.md#withhandlers type EventHandler = Function; type HandleCreators = { [handlerName: string]: mapper; }; - type HandleCreatorsFactory = (initialProps: TOutter) => HandleCreators; - export function withHandlers( - handlerCreators: HandleCreators | HandleCreatorsFactory - ): ComponentEnhancer; + type HandleCreatorsFactory = (initialProps: TOutter) => HandleCreators; + export function withHandlers( + handlerCreators: HandleCreators | HandleCreatorsFactory + ): InferableComponentEnhancerWithProps; // defaultProps: https://github.com/acdlite/recompose/blob/master/docs/API.md#defaultprops - export function defaultProps( - props: Object - ): InferableComponentEnhancer; + export function defaultProps( + props: T + ): DefaultingInferableComponentEnhancer; // renameProp: https://github.com/acdlite/recompose/blob/master/docs/API.md#renameProp export function renameProp( @@ -90,69 +110,97 @@ declare module 'recompose' { ): ComponentEnhancer; // withState: https://github.com/acdlite/recompose/blob/master/docs/API.md#withState - export function withState( - stateName: string, - stateUpdaterName: string, - initialState: any | mapper - ): ComponentEnhancer void }*/, TOutter>; + type stateProps< + TState, + TStateName extends string, + TStateUpdaterName extends string + > = ( + {[stateName in TStateName]: TState} & + {[stateUpdateName in TStateUpdaterName]: (state: TState) => TState} + ) + export function withState< + TOutter, + TState, + TStateName extends string, + TStateUpdaterName extends string + >( + stateName: TStateName, + stateUpdaterName: TStateUpdaterName, + initialState: TState | mapper + ): InferableComponentEnhancerWithProps< + stateProps, + TOutter + >; // withReducer: https://github.com/acdlite/recompose/blob/master/docs/API.md#withReducer type reducer = (s: TState, a: TAction) => TState; - export function withReducer( - stateName: string, - dispatchName: string, + type reducerProps< + TState, + TAction, + TStateName extends string, + TDispatchName extends string + > = ( + {[stateName in TStateName]: TState} & + {[dispatchName in TDispatchName]: (a: TAction) => void} + ) + export function withReducer< + TOutter, + TState, + TAction, + TStateName extends string, + TDispatchName extends string + >( + stateName: TStateName, + dispatchName: TDispatchName, reducer: reducer, - initialState: TState - ): ComponentEnhancer; - export function withReducer( - stateName: string, - dispatchName: string, - reducer: reducer, - initialState: (props: TOutter) => TState - ): ComponentEnhancer; + initialState: TState | mapper + ): InferableComponentEnhancerWithProps< + reducerProps, + TOutter + >; // branch: https://github.com/acdlite/recompose/blob/master/docs/API.md#branch export function branch( test: predicate, - trueEnhancer: InferableComponentEnhancer, - falseEnhancer?: InferableComponentEnhancer + trueEnhancer: ComponentEnhancer | InferableComponentEnhancer<{}>, + falseEnhancer?: ComponentEnhancer | InferableComponentEnhancer<{}> ): ComponentEnhancer; // renderComponent: https://github.com/acdlite/recompose/blob/master/docs/API.md#renderComponent - export function renderComponent( - component: string | Component + export function renderComponent( + component: string | Component ): ComponentEnhancer; // renderNothing: https://github.com/acdlite/recompose/blob/master/docs/API.md#renderNothing - export const renderNothing: InferableComponentEnhancer; + export const renderNothing: InferableComponentEnhancer<{}>; // shouldUpdate: https://github.com/acdlite/recompose/blob/master/docs/API.md#shouldUpdate export function shouldUpdate( test: predicateDiff - ): InferableComponentEnhancer; + ): InferableComponentEnhancer<{}>; // pure: https://github.com/acdlite/recompose/blob/master/docs/API.md#pure - export function pure)> - (component: TComp): TComp; + export function pure + (component: Component): Component; // onlyUpdateForKeys: https://github.com/acdlite/recompose/blob/master/docs/API.md#onlyUpdateForKeys export function onlyUpdateForKeys( propKeys: Array - ) : InferableComponentEnhancer; + ) : InferableComponentEnhancer<{}>; // onlyUpdateForPropTypes: https://github.com/acdlite/recompose/blob/master/docs/API.md#onlyUpdateForPropTypes - export const onlyUpdateForPropTypes: InferableComponentEnhancer; + export const onlyUpdateForPropTypes: InferableComponentEnhancer<{}>; // withContext: https://github.com/acdlite/recompose/blob/master/docs/API.md#withContext export function withContext( childContextTypes: ValidationMap, getChildContext: mapper - ) : InferableComponentEnhancer; + ) : InferableComponentEnhancer<{}>; // getContext: https://github.com/acdlite/recompose/blob/master/docs/API.md#getContext - export function getContext( + export function getContext( contextTypes: ValidationMap - ) : InferableComponentEnhancer; + ) : InferableComponentEnhancer; interface ReactLifeCycleFunctionsThisArguments { props: TProps, @@ -180,10 +228,10 @@ declare module 'recompose' { export function lifecycle( spec: ReactLifeCycleFunctions - ): InferableComponentEnhancer; + ): InferableComponentEnhancer<{}>; // toClass: https://github.com/acdlite/recompose/blob/master/docs/API.md#toClass - export const toClass: InferableComponentEnhancer; + export const toClass: InferableComponentEnhancer<{}>; // Static property helpers: https://github.com/acdlite/recompose/blob/master/docs/API.md#static-property-helpers @@ -267,9 +315,9 @@ declare module 'recompose' { ): React.ComponentClass; // ??? // hoistStatics: https://github.com/acdlite/recompose/blob/master/docs/API.md#hoistStatics - export function hoistStatics( - hoc: InferableComponentEnhancer - ): InferableComponentEnhancer; + export function hoistStatics( + hoc: InferableComponentEnhancer + ): InferableComponentEnhancer; diff --git a/types/recompose/recompose-tests.tsx b/types/recompose/recompose-tests.tsx index a8bf57ee74..4ac39cc624 100644 --- a/types/recompose/recompose-tests.tsx +++ b/types/recompose/recompose-tests.tsx @@ -1,6 +1,5 @@ import * as React from "react"; import { - Component, // Higher-order components mapProps, withProps, withPropsOnChange, withHandlers, defaultProps, renameProp, renameProps, flattenProp, @@ -27,67 +26,107 @@ import baconConfig from "recompose/baconObservableConfig"; import kefirConfig from "recompose/kefirObservableConfig"; function testMapProps() { - interface InnerProps { inn: number } + interface InnerProps { + inn: number + other: string + } interface OutterProps { out: string } - const innerComponent = ({inn}: InnerProps) =>

{inn}
; + const InnerComponent = ({inn}: InnerProps) =>
{inn}
; - const enhancer = mapProps((props: OutterProps) => ({ inn: 123 } as InnerProps)); - const enhanced: React.ComponentClass = enhancer(innerComponent); + const enhancer = mapProps((props: OutterProps) => ({ inn: 123 })); + const Enhanced = enhancer(InnerComponent); + const rendered = ( + + ) } function testWithProps() { interface InnerProps { inn: number } - interface OutterProps { out: string } - const innerComponent = ({inn}: InnerProps) =>
{inn}
; + interface OutterProps { out: number } + const InnerComponent = ({inn}: InnerProps) =>
{inn}
; - const enhancer = withProps((props: OutterProps) => ({ inn: 123 } as InnerProps)); - const enhanced: React.ComponentClass = enhancer(innerComponent); + const enhancer = withProps((props: OutterProps) => ({ inn: props.out })); + const Enhanced = enhancer(InnerComponent); + const rendered = ( + + ) - const enhancer2 = withProps({ inn: 123 } as InnerProps); - const enhanced2: React.ComponentClass = enhancer2(innerComponent); + const enhancer2 = withProps({ inn: 123 }); + const Enhanced2 = enhancer2(InnerComponent); + const Rendered2 = ( + + ) } function testWithPropsOnChange() { interface InnerProps { inn: number } - interface OutterProps { out: string } - const innerComponent = ({inn}: InnerProps) =>
{inn}
; + interface OutterProps { out: number } + const InnerComponent = ({inn}: InnerProps) =>
{inn}
; - const enhancer = withPropsOnChange( - (props: OutterProps, nextProps: OutterProps) => true, - (props: OutterProps) => ({ inn: 123 } as InnerProps)); - const enhanced: React.ComponentClass = enhancer(innerComponent); + const enhancer = withProps((props: OutterProps) => ({ inn: props.out })); + const Enhanced = enhancer(InnerComponent); + const rendered = ( + + ) - const enhancer2 = withPropsOnChange( - [ "out" ], - (props: OutterProps) => ({ inn: 123 } as InnerProps)); - const enhanced2: React.ComponentClass = enhancer2(innerComponent); + const enhancer2 = withProps({ inn: 123 }); + const Enhanced2 = enhancer2(InnerComponent); + const Rendered2 = ( + + ) } function testWithHandlers() { - interface InnerProps { onSubmit: React.MouseEventHandler; onChange: Function; } - interface OutterProps { out: string } - const innerComponent = ({onChange, onSubmit}: InnerProps) => + interface InnerProps { + onSubmit: React.MouseEventHandler; + onChange: Function; + foo: string; + } + interface HandlerProps { + onSubmit: React.MouseEventHandler; + onChange: Function; + } + interface OutterProps { out: number; } + const InnerComponent: React.StatelessComponent = ({onChange, onSubmit}) =>
; - const enhancer = withHandlers({ - onChange: (props: OutterProps) => (e: any) => {}, - onSubmit: (props: OutterProps) => (e: any) => {}, + const enhancer = withHandlers({ + onChange: (props) => (e: any) => {}, + onSubmit: (props) => (e: React.MouseEvent) => {}, }); - const enhanced: React.ComponentClass = enhancer(innerComponent); + const Enhanced = enhancer(InnerComponent); + const rendered = ( + + ) - const enhancer2 = withHandlers((props: OutterProps) => ({ - onChange: (props: OutterProps) => (e: any) => {}, - onSubmit: (props: OutterProps) => (e: any) => {}, + const enhancer2 = withHandlers((props) => ({ + onChange: (props) => (e: any) => {}, + onSubmit: (props) => (e: React.MouseEvent) => {}, })); - const enhanced2: React.ComponentClass = enhancer2(innerComponent); + const Enhanced2 = enhancer2(InnerComponent); + const rendered2 = ( + + ) } function testDefaultProps() { - interface Props { a?: string; b?: number; } + interface Props { a: string; b: number; c: number; } const innerComponent = ({a, b}: Props) =>
{a}, {b}
; const enhancer = defaultProps({ a: "answer", b: 42 }); - const enhanced: React.StatelessComponent = enhancer JSX.Element>(innerComponent); + const Enhanced = enhancer(innerComponent); + const rendered = ( + + ) } function testRenameProp() { @@ -118,35 +157,59 @@ function testFlattenProp() { } function testWithState() { - interface InnerProps { count: number; setCount: (count: number) => void } + interface InnerProps { count: number; setCount: (count: number) => number } interface OutterProps { title: string } - const innerComponent: React.StatelessComponent = (props) => + const InnerComponent: React.StatelessComponent = (props) =>
props.setCount(0)}>
; - const enhancer = withState("count", "setCount", 0); - const enhanced: React.ComponentClass = enhancer(innerComponent); + // We can't infer types for TOutter with this form because + // Typescript only allows all or nothing + // when defining generics. You can't infer some and define other. + // For TOutter to be defined as not "{}" we would have to define + // all the generics. + const enhancer = withState("count", "setCount", 0); + const Enhanced = enhancer(InnerComponent); + const rendered = ( + + ); - const enhancer2 = withState("count", "setCount", + // Here we're able to infer TOutter since it's defined in the initial state + // function and Typescript is able to infer it from there. + const enhancer2 = withState("count", "setCount", (p: OutterProps) => p.title.length); - const enhanced2: React.ComponentClass = enhancer2(innerComponent); + const Enhanced2 = enhancer2(InnerComponent); + const rendered2 = ( + + ); } function testWithReducer() { interface State { count: number } interface Action { type: string } interface InnerProps { title: string; count: number; dispatch: (a: Action) => void; } - interface OutterProps { title: string; } - const innerComponent: React.StatelessComponent = (props: InnerProps) => + interface OutterProps { title: string; bar: number; } + const InnerComponent: React.StatelessComponent = (props) =>
props.dispatch({type: "INCREMENT"})}>
; + // Same issue here inferring TOutter as with the "withState" form. const enhancer = withReducer("count", "dispatch", (s: number, a: Action) => s + 1, 0); - const enhanced: React.ComponentClass = enhancer(innerComponent); + const Enhanced = enhancer(InnerComponent); + const rendered = ( + + ); + // Here we successfully infer TOutter from the initial state function const enhancer2 = withReducer("count", "dispatch", (s: number, a: Action) => s + 1, (props: OutterProps) => props.title.length); - const enhanced2: React.ComponentClass = enhancer2(innerComponent); + const Enhanced2 = enhancer2(InnerComponent); + const rendered2 = ( + + ); } function testBranch() {