Skip to content

Commit c526e3b

Browse files
committed
Get all type tests working and add some additional tests
1 parent 2debcb5 commit c526e3b

File tree

3 files changed

+77
-41
lines changed

3 files changed

+77
-41
lines changed

etc/react-redux.api.md

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
55
```ts
66

7-
/// <reference types="hoist-non-react-statics" />
87
/// <reference types="react" />
98

109
import { Action } from 'redux';
@@ -15,49 +14,40 @@ import { ComponentClass } from 'react';
1514
import { ComponentType } from 'react';
1615
import { Context } from 'react';
1716
import { Dispatch } from 'redux';
18-
import { ForwardRefExoticComponent } from 'react';
19-
import hoistStatics from 'hoist-non-react-statics';
20-
import { MemoExoticComponent } from 'react';
21-
import { NamedExoticComponent } from 'react';
22-
import { NonReactStatics } from 'hoist-non-react-statics';
17+
import type { NonReactStatics } from 'hoist-non-react-statics';
2318
import { default as React_2 } from 'react';
2419
import { ReactNode } from 'react';
25-
import { RefAttributes } from 'react';
2620
import { Store } from 'redux';
2721

2822
// @public (undocumented)
29-
export type AdvancedComponentDecorator<TProps, TOwnProps> = (component: ComponentType<TProps>) => NamedExoticComponent<TOwnProps>;
23+
export type AdvancedComponentDecorator<TProps, TOwnProps> = (component: ComponentType<TProps>) => ComponentType<TOwnProps>;
3024

3125
// @public (undocumented)
3226
export type AnyIfEmpty<T extends object> = keyof T extends never ? any : T;
3327

3428
export { batch }
3529

3630
// @public (undocumented)
37-
export const connect: (mapStateToProps: MapStateToPropsParam<unknown, unknown, DefaultRootState>, mapDispatchToProps: unknown, mergeProps: MergeProps<unknown, unknown, unknown, unknown>, { pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, ...extraOptions }?: ConnectOptions<DefaultRootState, {}, {}, {}>) => <WC extends ComponentType< {}>>(WrappedComponent: WC) => (ForwardRefExoticComponent<RefAttributes<unknown>> & {
38-
WrappedComponent: WC;
39-
} & NonReactStatics<WC, {}>) | ((({
40-
<TOwnProps>(props: ConnectProps & TOwnProps): JSX.Element;
41-
displayName: string;
42-
} | MemoExoticComponent< {
43-
<TOwnProps>(props: ConnectProps & TOwnProps): JSX.Element;
44-
displayName: string;
45-
}>) & {
46-
WrappedComponent: WC;
47-
}) & NonReactStatics<WC, {}>);
48-
49-
// @public (undocumented)
50-
export function connectAdvanced<S, TProps, TOwnProps, TFactoryOptions extends AnyObject = {}>(selectorFactory: SelectorFactory<S, TProps, unknown, unknown>, { getDisplayName, methodName, shouldHandleStateChanges, forwardRef, context, ...connectOptions }?: ConnectAdvancedOptions & Partial<TFactoryOptions>): <WC extends React_2.ComponentType<{}>>(WrappedComponent: WC) => (React_2.ForwardRefExoticComponent<React_2.RefAttributes<unknown>> & {
51-
WrappedComponent: WC;
52-
} & hoistStatics.NonReactStatics<WC, {}>) | ((({
53-
<TOwnProps_1>(props: ConnectProps & TOwnProps_1): JSX.Element;
54-
displayName: string;
55-
} | React_2.MemoExoticComponent<{
56-
<TOwnProps_1>(props: ConnectProps & TOwnProps_1): JSX.Element;
57-
displayName: string;
58-
}>) & {
59-
WrappedComponent: WC;
60-
}) & hoistStatics.NonReactStatics<WC, {}>);
31+
export const connect: {
32+
(): InferableComponentEnhancer<DispatchProp>;
33+
<TStateProps = {}, no_dispatch = {}, TOwnProps = {}, State = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>): InferableComponentEnhancerWithProps<TStateProps & DispatchProp<AnyAction>, TOwnProps>;
34+
<no_state = {}, TDispatchProps = {}, TOwnProps_1 = {}>(mapStateToProps: null | undefined, mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps, TOwnProps_1>): InferableComponentEnhancerWithProps<TDispatchProps, TOwnProps_1>;
35+
<no_state_1 = {}, TDispatchProps_1 = {}, TOwnProps_2 = {}>(mapStateToProps: null | undefined, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_1, TOwnProps_2>): InferableComponentEnhancerWithProps<ResolveThunks<TDispatchProps_1>, TOwnProps_2>;
36+
<TStateProps_1 = {}, TDispatchProps_2 = {}, TOwnProps_3 = {}, State_1 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_1, TOwnProps_3, State_1>, mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps_2, TOwnProps_3>): InferableComponentEnhancerWithProps<TStateProps_1 & TDispatchProps_2, TOwnProps_3>;
37+
<TStateProps_2 = {}, TDispatchProps_3 = {}, TOwnProps_4 = {}, State_2 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_2, TOwnProps_4, State_2>, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_3, TOwnProps_4>): InferableComponentEnhancerWithProps<TStateProps_2 & ResolveThunks<TDispatchProps_3>, TOwnProps_4>;
38+
<no_state_2 = {}, no_dispatch_1 = {}, TOwnProps_5 = {}, TMergedProps = {}>(mapStateToProps: null | undefined, mapDispatchToProps: null | undefined, mergeProps: MergeProps<undefined, undefined, TOwnProps_5, TMergedProps>): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps_5>;
39+
<TStateProps_3 = {}, no_dispatch_2 = {}, TOwnProps_6 = {}, TMergedProps_1 = {}, State_3 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_3, TOwnProps_6, State_3>, mapDispatchToProps: null | undefined, mergeProps: MergeProps<TStateProps_3, undefined, TOwnProps_6, TMergedProps_1>): InferableComponentEnhancerWithProps<TMergedProps_1, TOwnProps_6>;
40+
<no_state_3 = {}, TDispatchProps_4 = {}, TOwnProps_7 = {}, TMergedProps_2 = {}>(mapStateToProps: null | undefined, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_4, TOwnProps_7>, mergeProps: MergeProps<undefined, TDispatchProps_4, TOwnProps_7, TMergedProps_2>): InferableComponentEnhancerWithProps<TMergedProps_2, TOwnProps_7>;
41+
<TStateProps_4 = {}, no_dispatch_3 = {}, TOwnProps_8 = {}, State_4 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_4, TOwnProps_8, State_4>, mapDispatchToProps: null | undefined, mergeProps: null | undefined, options: ConnectOptions<State_4, TStateProps_4, TOwnProps_8, {}>): InferableComponentEnhancerWithProps<DispatchProp<AnyAction> & TStateProps_4, TOwnProps_8>;
42+
<TStateProps_5 = {}, TDispatchProps_5 = {}, TOwnProps_9 = {}>(mapStateToProps: null | undefined, mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps_5, TOwnProps_9>, mergeProps: null | undefined, options: ConnectOptions<{}, TStateProps_5, TOwnProps_9, {}>): InferableComponentEnhancerWithProps<TDispatchProps_5, TOwnProps_9>;
43+
<TStateProps_6 = {}, TDispatchProps_6 = {}, TOwnProps_10 = {}>(mapStateToProps: null | undefined, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_6, TOwnProps_10>, mergeProps: null | undefined, options: ConnectOptions<{}, TStateProps_6, TOwnProps_10, {}>): InferableComponentEnhancerWithProps<ResolveThunks<TDispatchProps_6>, TOwnProps_10>;
44+
<TStateProps_7 = {}, TDispatchProps_7 = {}, TOwnProps_11 = {}, State_5 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_7, TOwnProps_11, State_5>, mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps_7, TOwnProps_11>, mergeProps: null | undefined, options: ConnectOptions<State_5, TStateProps_7, TOwnProps_11, {}>): InferableComponentEnhancerWithProps<TStateProps_7 & TDispatchProps_7, TOwnProps_11>;
45+
<TStateProps_8 = {}, TDispatchProps_8 = {}, TOwnProps_12 = {}, State_6 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_8, TOwnProps_12, State_6>, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_8, TOwnProps_12>, mergeProps: null | undefined, options: ConnectOptions<State_6, TStateProps_8, TOwnProps_12, {}>): InferableComponentEnhancerWithProps<TStateProps_8 & ResolveThunks<TDispatchProps_8>, TOwnProps_12>;
46+
<TStateProps_9 = {}, TDispatchProps_9 = {}, TOwnProps_13 = {}, TMergedProps_3 = {}, State_7 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_9, TOwnProps_13, State_7>, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_9, TOwnProps_13>, mergeProps: MergeProps<TStateProps_9, TDispatchProps_9, TOwnProps_13, TMergedProps_3>, options?: ConnectOptions<State_7, TStateProps_9, TOwnProps_13, TMergedProps_3> | undefined): InferableComponentEnhancerWithProps<TMergedProps_3, TOwnProps_13>;
47+
};
48+
49+
// @public (undocumented)
50+
export function connectAdvanced<S, TProps, TOwnProps, TFactoryOptions = {}>(selectorFactory: SelectorFactory<S, TProps, unknown, unknown>, { getDisplayName, methodName, shouldHandleStateChanges, forwardRef, context, ...connectOptions }?: ConnectAdvancedOptions & Partial<TFactoryOptions>): AdvancedComponentDecorator<TProps, TOwnProps & ConnectProps>;
6151

6252
// @public (undocumented)
6353
export interface ConnectAdvancedOptions {
@@ -76,10 +66,13 @@ export interface ConnectAdvancedOptions {
7666
}
7767

7868
// @public (undocumented)
79-
export type ConnectedComponent<C extends ComponentType<any>, P> = NamedExoticComponent<JSX.LibraryManagedAttributes<C, P>> & NonReactStatics<C> & {
69+
export type ConnectedComponent<C extends ComponentType<any>, P> = ComponentType<P> & NonReactStatics<C> & {
8070
WrappedComponent: C;
8171
};
8272

73+
// @public
74+
export type ConnectedProps<TConnector> = TConnector extends InferableComponentEnhancerWithProps<infer TInjectedProps, any> ? unknown extends TInjectedProps ? TConnector extends InferableComponentEnhancer<infer TInjectedProps> ? TInjectedProps : never : TInjectedProps : never;
75+
8376
// @public (undocumented)
8477
export interface ConnectProps {
8578
// (undocumented)

test/tsconfig.test.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": "../tsconfig.base.json",
2+
"extends": "../tsconfig.json",
33
"compilerOptions": {
44
"allowSyntheticDefaultImports": true,
55
"esModuleInterop": true,

test/typetests/react-redux-types.typetest.tsx

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { Component, ReactElement } from 'react'
33
import * as React from 'react'
44
import * as ReactDOM from 'react-dom'
55
import { Store, Dispatch, bindActionCreators, AnyAction } from 'redux'
6-
import { connect, Provider } from '../../src/index'
6+
import { connect, Provider, ConnectedProps } from '../../src/index'
7+
import { expectType } from '../typeTestHelpers'
78

89
import objectAssign from 'object-assign'
910

@@ -38,8 +39,8 @@ function mapDispatchToProps(dispatch: Dispatch<AnyAction>) {
3839

3940
connect(mapStateToProps, mapDispatchToProps)(Counter)
4041

41-
@connect(mapStateToProps)
4242
class CounterContainer extends Component<any, any> {}
43+
const ConnectedCounterContainer = connect(mapStateToProps)(CounterContainer)
4344

4445
// Ensure connect's first two arguments can be replaced by wrapper functions
4546
interface ICounterStateProps {
@@ -48,19 +49,21 @@ interface ICounterStateProps {
4849
interface ICounterDispatchProps {
4950
onIncrement: () => void
5051
}
51-
connect<ICounterStateProps, ICounterDispatchProps, {}>(
52+
connect<ICounterStateProps, ICounterDispatchProps, {}, CounterState>(
5253
() => mapStateToProps,
5354
() => mapDispatchToProps
5455
)(Counter)
5556
// only first argument
56-
connect<ICounterStateProps, {}, {}>(() => mapStateToProps)(Counter)
57+
connect<ICounterStateProps, {}, {}, CounterState>(() => mapStateToProps)(
58+
Counter
59+
)
5760
// wrap only one argument
58-
connect<ICounterStateProps, ICounterDispatchProps, {}>(
61+
connect<ICounterStateProps, ICounterDispatchProps, {}, CounterState>(
5962
mapStateToProps,
6063
() => mapDispatchToProps
6164
)(Counter)
6265
// with extra arguments
63-
connect<ICounterStateProps, ICounterDispatchProps, {}>(
66+
connect<ICounterStateProps, ICounterDispatchProps, {}, {}, CounterState>(
6467
() => mapStateToProps,
6568
() => mapDispatchToProps,
6669
(s: ICounterStateProps, d: ICounterDispatchProps) => objectAssign({}, s, d),
@@ -240,7 +243,7 @@ class TestComponent extends Component<TestProp, TestState> {}
240243
const WrappedTestComponent = connect()(TestComponent)
241244

242245
// return value of the connect()(TestComponent) is of the type TestComponent
243-
let ATestComponent: typeof TestComponent
246+
let ATestComponent: React.ComponentType<TestProp>
244247
ATestComponent = TestComponent
245248
ATestComponent = WrappedTestComponent
246249

@@ -249,6 +252,9 @@ let anElement: ReactElement<TestProp>
249252
;<WrappedTestComponent property1={42} />
250253
;<ATestComponent property1={42} />
251254

255+
// @ts-expect-error
256+
;<ATestComponent property1={42} dummyField={123} />
257+
252258
class NonComponent {}
253259
// this doesn't compile
254260
// @ts-expect-error
@@ -359,3 +365,40 @@ namespace TestTOwnPropsInference {
359365
// @ts-expect-error
360366
React.createElement(ConnectedWithTypeHint, { missingOwn: true })
361367
}
368+
369+
namespace ConnectedPropsTest {
370+
interface RootState {
371+
isOn: boolean
372+
}
373+
374+
const mapState1 = (state: RootState) => ({
375+
isOn: state.isOn,
376+
})
377+
378+
const mapDispatch1 = {
379+
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
380+
}
381+
382+
const connector1 = connect(mapState1, mapDispatch1)
383+
384+
// The inferred type will look like:
385+
// {isOn: boolean, toggleOn: () => void}
386+
type PropsFromRedux1 = ConnectedProps<typeof connector1>
387+
388+
expectType<{ isOn: boolean; toggleOn: () => void }>({} as PropsFromRedux1)
389+
390+
const exampleThunk = (id: number) => async (dispatch: Dispatch) => {
391+
return 'test'
392+
}
393+
394+
const mapDispatch2 = { exampleThunk }
395+
396+
// Connect should "resolve thunks", so that instead of typing the return value of the
397+
// prop as the thunk function, it dives down and uses the return value of the thunk function itself
398+
const connector2 = connect(null, mapDispatch2)
399+
type PropsFromRedux2 = ConnectedProps<typeof connector2>
400+
401+
expectType<{ exampleThunk: (id: number) => Promise<string> }>(
402+
{} as PropsFromRedux2
403+
)
404+
}

0 commit comments

Comments
 (0)