import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Store, Dispatch, AnyAction, ActionCreator, createStore, bindActionCreators, ActionCreatorsMapObject, Reducer, } from 'redux'; import { Connect, connect, ConnectedProps, Provider, DispatchProp, MapStateToProps, Options, ReactReduxContext, ReactReduxContextValue, Selector, shallowEqual, MapDispatchToProps, useDispatch, useSelector, useStore, createDispatchHook, createSelectorHook, createStoreHook, TypedUseSelectorHook, } from 'react-redux'; import objectAssign = require('object-assign'); // // Quick Start // https://github.com/rackt/react-redux/blob/master/docs/quick-start.md#quick-start // // Test cases written in a way to isolate types and variables and verify the // output of `connect` to make sure the signature is what is expected function Empty() { interface OwnProps { dispatch: Dispatch; foo: string; } class TestComponent extends React.Component { } const Test = connect()(TestComponent); const verify = ; } function MapState() { interface OwnProps { foo: string; } interface StateProps { bar: number; } class TestComponent extends React.Component { } const mapStateToProps = (_: any) => ({ bar: 1 }); const Test = connect( mapStateToProps )(TestComponent); const verify = ; } function MapStateWithDispatchProp() { interface OwnProps { foo: string; } interface StateProps { bar: number; dispatch: Dispatch; } class TestComponent extends React.Component { } const mapStateToProps = (_: any) => ({ bar: 1 }); const Test = connect( mapStateToProps )(TestComponent); const verify = ; } function MapStateFactory() { interface OwnProps { foo: string; } interface StateProps { bar: number; } class TestComponent extends React.Component { } const mapStateToProps = () => () => ({ bar: 1 }); const Test = connect( mapStateToProps )(TestComponent); const verify = ; } function MapDispatch() { interface OwnProps { foo: string; } interface DispatchProps { onClick: () => void; } class TestComponent extends React.Component { } const mapDispatchToProps = ({ onClick: () => { } }); const TestNull = connect( null, mapDispatchToProps, )(TestComponent); const verifyNull = ; const TestUndefined = connect( undefined, mapDispatchToProps, )(TestComponent); const verifyUndefined = ; } function MapDispatchUnion() { interface OwnProps { foo: string; } interface DispatchProps { onClick: () => void; } class TestComponent extends React.Component { } // We deliberately cast the right-hand side to `any` because otherwise // TypeScript would maintain the literal value, when we deliberately want to // test the union type here (as per the annotation). See // https://github.com/Microsoft/TypeScript/issues/30310#issuecomment-472218182. const mapDispatchToProps: MapDispatchToProps = {} as any; const TestNull = connect( null, mapDispatchToProps, )(TestComponent); const verifyNull = ; const TestUndefined = connect( undefined, mapDispatchToProps, )(TestComponent); const verifyUndefined = ; } function MapDispatchWithThunkActionCreators() { const simpleAction = (payload: boolean) => ({ type: 'SIMPLE_ACTION', payload, }); const thunkAction = (param1: number, param2: string) => ( async (dispatch: Dispatch, { foo }: OwnProps) => { return foo; } ); interface OwnProps { foo: string; } interface TestComponentProps extends OwnProps { simpleAction: typeof simpleAction; thunkAction(param1: number, param2: string): Promise; } class TestComponent extends React.Component { } const mapStateToProps = ({ foo }: { foo: string }) => ({ foo }); const mapDispatchToProps = { simpleAction, thunkAction }; const Test1 = connect(null, mapDispatchToProps)(TestComponent); const Test2 = connect(mapStateToProps, mapDispatchToProps)(TestComponent); const Test3 = connect( null, mapDispatchToProps, null, { storeKey: 'somekey' } )(TestComponent); const Test4 = connect( mapStateToProps, mapDispatchToProps, null, { storeKey: 'somekey' } )(TestComponent); const verify =
; ;
; } function MapManualDispatchThatLooksLikeThunk() { interface OwnProps { foo: string; } interface TestComponentProps extends OwnProps { remove: (item: string) => () => object; } class TestComponent extends React.Component { render() { return
; } } const mapStateToProps = ({ foo }: { foo: string }) => ({ foo }); function mapDispatchToProps(dispatch: Dispatch) { return { remove(item: string) { return () => dispatch({ type: 'REMOVE_ITEM', item }); } }; } const Test1 = connect(null, mapDispatchToProps)(TestComponent); const Test2 = connect(mapStateToProps, mapDispatchToProps)(TestComponent); const Test3 = connect( null, mapDispatchToProps, null, { storeKey: 'somekey' } )(TestComponent); const Test4 = connect( mapStateToProps, mapDispatchToProps, null, { storeKey: 'somekey' } )(TestComponent); const verify =
; ;
; } function MapStateAndDispatchObject() { interface ClickPayload { count: number; } const onClick: ActionCreator = () => ({ count: 1 }); const dispatchToProps = { onClick, }; interface OwnProps { foo: string; } interface StateProps { bar: number; } interface DispatchProps { onClick: ActionCreator; } const mapStateToProps = (_: any, __: OwnProps): StateProps => ({ bar: 1 }); class TestComponent extends React.Component { } const Test = connect( mapStateToProps, dispatchToProps, )(TestComponent); const verify = ; } function MapDispatchFactory() { interface OwnProps { foo: string; } interface DispatchProps { onClick: () => void; } class TestComponent extends React.Component { } const mapDispatchToPropsFactory = () => () => ({ onClick: () => { } }); const TestNull = connect( null, mapDispatchToPropsFactory, )(TestComponent); const verifyNull = ; const TestUndefined = connect( undefined, mapDispatchToPropsFactory, )(TestComponent); const verifyUndefined = ; } function MapStateAndDispatch() { interface OwnProps { foo: string; } interface StateProps { bar: number; } interface DispatchProps { onClick: () => void; } class TestComponent extends React.Component { } const mapStateToProps = () => ({ bar: 1 }); const mapDispatchToProps = () => ({ onClick: () => { } }); const Test = connect( mapStateToProps, mapDispatchToProps, )(TestComponent); const verify = ; } function MapStateFactoryAndDispatch() { interface OwnProps { foo: string; } interface StateProps { bar: number; } interface DispatchProps { onClick: () => void; } const mapStateToPropsFactory = () => () => ({ bar: 1 }); const mapDispatchToProps = () => ({ onClick: () => { } }); class TestComponent extends React.Component { } const Test = connect( mapStateToPropsFactory, mapDispatchToProps, )(TestComponent); const verify = ; } function MapStateFactoryAndDispatchFactory() { interface OwnProps { foo: string; } interface StateProps { bar: number; } interface DispatchProps { onClick: () => void; } const mapStateToPropsFactory = () => () => ({ bar: 1 }); const mapDispatchToPropsFactory = () => () => ({ onClick: () => { } }); class TestComponent extends React.Component { } const Test = connect( mapStateToPropsFactory, mapDispatchToPropsFactory, )(TestComponent); const verify = ; } function MapStateAndDispatchAndMerge() { interface OwnProps { foo: string; } interface StateProps { bar: number; } interface DispatchProps { onClick: () => void; } class TestComponent extends React.Component { } const mapStateToProps = () => ({ bar: 1 }); const mapDispatchToProps = () => ({ onClick: () => { } }); const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps) => ( { ...stateProps, ...dispatchProps } ); const Test = connect( mapStateToProps, mapDispatchToProps, mergeProps, )(TestComponent); const verify = ; } function MapStateAndOptions() { interface State { state: string; } interface OwnProps { foo: string; } interface StateProps { bar: number; } interface DispatchProps { dispatch: Dispatch; } class TestComponent extends React.Component { } const mapStateToProps = (state: State) => ({ bar: 1 }); const areStatePropsEqual = (next: StateProps, current: StateProps) => true; const Test = connect( mapStateToProps, null, null, { pure: true, areStatePropsEqual, } )(TestComponent); const verify = ; } interface CounterState { counter: number; } declare var increment: () => { type: string }; class Counter extends React.Component { render() { return ( ); } } function mapStateToProps(state: CounterState) { return { value: state.counter }; } // Which action creators does it want to receive by props? function mapDispatchToProps(dispatch: Dispatch) { return { onIncrement: () => dispatch(increment()) }; } // Ensure connect's first two arguments can be replaced by wrapper functions interface CounterStateProps { value: number; } interface CounterDispatchProps { onIncrement: () => void; } // with higher order functions connect( () => mapStateToProps, () => mapDispatchToProps )(Counter); // with higher order functions using parameters connect( (initialState: CounterState, ownProps) => mapStateToProps, (dispatch: Dispatch, ownProps) => mapDispatchToProps )(Counter); // only first argument connect( () => mapStateToProps )(Counter); // wrap only one argument connect( mapStateToProps, () => mapDispatchToProps )(Counter); // with extra arguments connect( () => mapStateToProps, () => mapDispatchToProps, (s: CounterStateProps, d: CounterDispatchProps) => objectAssign({}, s, d), { pure: true } )(Counter); class App extends React.Component { render() { return null; } } const targetEl = document.getElementById('root'); ReactDOM.render(( {() => } ), targetEl); // // API // https://github.com/rackt/react-redux/blob/master/docs/api.md // declare var store: Store; class MyRootComponent extends React.Component { } class TodoApp extends React.Component { } interface TodoState { todos: string[] | string; } interface TodoProps { userId: number; } interface DispatchProps { addTodo(userId: number, text: string): void; action: () => any; } declare var actionCreators: () => { action: () => any; }; declare var dispatchActionCreators: () => DispatchProps; declare var addTodo: () => { type: string; }; declare var todoActionCreators: { [type: string]: (...args: any[]) => any; }; declare var counterActionCreators: { [type: string]: (...args: any[]) => any; }; ReactDOM.render( {() => } , document.body ); // Inject just dispatch and don't listen to store const AppWrap = (props: DispatchProp & { children?: React.ReactNode }) =>
; const WrappedApp = connect()(AppWrap); ; // Inject dispatch and every field in the global state connect((state: TodoState) => state)(TodoApp); // Inject dispatch and todos function mapStateToProps2(state: TodoState) { return { todos: state.todos }; } export default connect(mapStateToProps2)(TodoApp); connect(mapStateToProps2, actionCreators)(TodoApp); function mapDispatchToProps2(dispatch: Dispatch) { return { actions: bindActionCreators(actionCreators, dispatch) }; } connect(mapStateToProps2, mapDispatchToProps2)(TodoApp); // Inject todos and a specific action creator (addTodo) function mapDispatchToProps3(dispatch: Dispatch) { return bindActionCreators({ addTodo }, dispatch); } connect(mapStateToProps2, mapDispatchToProps3)(TodoApp); // Inject todos, todoActionCreators as todoActions, and counterActionCreators as counterActions function mapDispatchToProps4(dispatch: Dispatch) { return { todoActions: bindActionCreators(todoActionCreators, dispatch), counterActions: bindActionCreators(counterActionCreators, dispatch) }; } connect(mapStateToProps2, mapDispatchToProps4)(TodoApp); // Inject todos, and todoActionCreators and counterActionCreators together as actions function mapDispatchToProps5(dispatch: Dispatch) { return { actions: bindActionCreators(objectAssign({}, todoActionCreators, counterActionCreators), dispatch) }; } connect(mapStateToProps2, mapDispatchToProps5)(TodoApp); // Inject todos, and all todoActionCreators and counterActionCreators directly as props function mapDispatchToProps6(dispatch: Dispatch) { return bindActionCreators(objectAssign({}, todoActionCreators, counterActionCreators), dispatch); } connect(mapStateToProps2, mapDispatchToProps6)(TodoApp); // Inject todos of a specific user depending on props function mapStateToProps3(state: TodoState, ownProps: TodoProps): TodoState { return { todos: state.todos[ownProps.userId] }; } connect(mapStateToProps3)(TodoApp); // Inject todos of a specific user depending on props, and inject props.userId into the action function mergeProps(stateProps: TodoState, dispatchProps: DispatchProps, ownProps: TodoProps): DispatchProps & TodoState & TodoProps { return objectAssign({}, ownProps, dispatchProps, { todos: stateProps.todos[ownProps.userId], addTodo: (text: string) => dispatchProps.addTodo(ownProps.userId, text) }); } connect(mapStateToProps2, dispatchActionCreators, mergeProps)(MyRootComponent); // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/14622#issuecomment-279820358 // Allow for undefined mapStateToProps connect(undefined, mapDispatchToProps6)(TodoApp); interface TestProp { property1: number; someOtherProperty?: string; } interface TestState { isLoaded: boolean; state1: number; } class TestComponent extends React.Component { } const WrappedTestComponent = connect()(TestComponent); // return value of the connect()(TestComponent) is assignable to a ComponentClass // ie: DispatchProp has been removed through decoration const ADecoratedTestComponent: React.NamedExoticComponent = WrappedTestComponent; ; const ATestComponent: React.NamedExoticComponent = TestComponent; // $ExpectError // stateless functions interface HelloMessageProps { dispatch: Dispatch; name: string; } const HelloMessage: React.StatelessComponent = (props) => { return
Hello {props.name}
; }; const ConnectedHelloMessage = connect()(HelloMessage); ReactDOM.render(, document.getElementById('content')); // stateless functions that uses mapStateToProps and mapDispatchToProps function TestStatelessFunctionWithMapArguments() { interface GreetingProps { name: string; onClick: () => void; } function Greeting(props: GreetingProps) { return
Hello {props.name}
; } const mapStateToProps = (state: any, ownProps: GreetingProps) => { return { name: 'Connected! ' + ownProps.name }; }; const mapDispatchToProps = (dispatch: Dispatch, ownProps: GreetingProps) => { return { onClick: () => { dispatch({ type: 'GREETING', name: ownProps.name }); } }; }; const ConnectedGreeting = connect( mapStateToProps, mapDispatchToProps )(Greeting); } // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8787 function TestTOwnPropsInference() { interface OwnProps { own: string; } interface StateProps { state: string; } class OwnPropsComponent extends React.Component { render() { return
; } } function mapStateToPropsWithoutOwnProps(state: any): StateProps { return { state: 'string' }; } function mapStateToPropsWithOwnProps(state: any, ownProps: OwnProps): StateProps { return { state: 'string' }; } const ConnectedWithoutOwnProps = connect(mapStateToPropsWithoutOwnProps)(OwnPropsComponent); const ConnectedWithOwnProps = connect(mapStateToPropsWithOwnProps)(OwnPropsComponent); const ConnectedWithTypeHint = connect(mapStateToPropsWithoutOwnProps)(OwnPropsComponent); // This should not compile, which is good. // React.createElement(ConnectedWithoutOwnProps, { anything: 'goes!' }); // This compiles, as expected. React.createElement(ConnectedWithOwnProps, { own: 'string' }); // This should not compile, which is good. // React.createElement(ConnectedWithOwnProps, { anything: 'goes!' }); // This compiles, as expected. React.createElement(ConnectedWithTypeHint, { own: 'string' }); // This should not compile, which is good. // React.createElement(ConnectedWithTypeHint, { anything: 'goes!' }); interface AllProps { own: string; state: string; } class AllPropsComponent extends React.Component { render() { return
; } } type PickedOwnProps = Pick; type PickedStateProps = Pick; const mapStateToPropsForPicked: MapStateToProps = (state: any): PickedStateProps => { return { state: "string" }; }; const ConnectedWithPickedOwnProps = connect(mapStateToPropsForPicked)(AllPropsComponent); ; } // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/16021 function TestMergedPropsInference() { interface StateProps { state: string; } interface DispatchProps { dispatch: string; } interface OwnProps { own: string; } interface MergedProps { merged: string; } class MergedPropsComponent extends React.Component { render() { return
; } } function mapStateToProps(state: any): StateProps { return { state: 'string' }; } function mapDispatchToProps(dispatch: Dispatch): DispatchProps { return { dispatch: 'string' }; } const ConnectedWithOwnAndState: React.NamedExoticComponent = connect( mapStateToProps, undefined, (stateProps: StateProps) => ({ merged: "merged", }), )(MergedPropsComponent); const ConnectedWithOwnAndDispatch: React.NamedExoticComponent = connect( undefined, mapDispatchToProps, (stateProps: undefined, dispatchProps: DispatchProps) => ({ merged: "merged", }), )(MergedPropsComponent); const ConnectedWithOwn: React.NamedExoticComponent = connect( undefined, undefined, () => ({ merged: "merged", }), )(MergedPropsComponent); } function Issue16652() { interface PassedProps { commentIds: string[]; } interface GeneratedStateProps { comments: Array<({ id: string } | undefined)>; } class CommentList extends React.Component { } const mapStateToProps = (state: any, ownProps: PassedProps): GeneratedStateProps => { return { comments: ownProps.commentIds.map(id => ({ id })), }; }; const ConnectedCommentList = connect(mapStateToProps)( CommentList ); ; } function Issue15463() { interface SpinnerProps { showGlobalSpinner: boolean; } class SpinnerClass extends React.Component { render() { return (
); } } const Spinner = connect((state: any) => { return { showGlobalSpinner: true }; })(SpinnerClass); ; } function RemoveInjectedAndPassOnRest() { interface TProps { showGlobalSpinner: boolean; foo: string; } class SpinnerClass extends React.Component { render() { return (
); } } const Spinner = connect((state: any) => { return { showGlobalSpinner: true }; })(SpinnerClass); ; } function TestControlledComponentWithoutDispatchProp() { interface MyState { count: number; } interface MyProps { label: string; // `dispatch` is optional, but setting it to anything // other than Dispatch will cause an error // // dispatch: Dispatch; // OK // dispatch: number; // ERROR } function mapStateToProps(state: MyState) { return { label: `The count is ${state.count}`, }; } class MyComponent extends React.Component { render() { return {this.props.label}; } } const MyFuncComponent = (props: MyProps) => ( {props.label} ); const MyControlledComponent = connect(mapStateToProps)(MyComponent); const MyControlledFuncComponent = connect(mapStateToProps)(MyFuncComponent); } function TestDispatchToPropsAsObject() { const onClick: ActionCreator<{}> = () => ({}); const mapStateToProps = (state: any) => { return { title: state.app.title as string, }; }; const dispatchToProps = { onClick, }; type Props = { title: string; } & typeof dispatchToProps; const HeaderComponent: React.StatelessComponent = (props) => { return

{props.title}

; }; const Header = connect(mapStateToProps, dispatchToProps)(HeaderComponent);
; } function TestInferredFunctionalComponentWithExplicitOwnProps() { interface Props { title: string; extraText: string; onClick: () => void; } const Header = connect( ( { app: { title } }: { app: { title: string } }, { extraText }: { extraText: string } ) => ({ title, extraText }), (dispatch) => ({ onClick: () => dispatch({ type: 'test' }) }) )(({ title, extraText, onClick }: Props) => { return

{title} {extraText}

; });
; } function TestInferredFunctionalComponentWithImplicitOwnProps() { interface Props { title: string; extraText: string; onClick: () => void; } const Header = connect( ( { app: { title } }: { app: { title: string } }, ) => ({ title, }), (dispatch) => ({ onClick: () => dispatch({ type: 'test' }) }) )(({ title, extraText, onClick }: Props) => { return

{title} {extraText}

; });
; } function TestWrappedComponent() { interface InnerProps { name: string; } const Inner: React.StatelessComponent = (props) => { return

{props.name}

; }; const mapStateToProps = (state: any) => { return { name: "Connected", }; }; const Connected = connect(mapStateToProps)(Inner); // `Inner` and `Connected.WrappedComponent` require explicit `name` prop const TestInner = (props: any) => ; const TestWrapped = (props: any) => ; // `Connected` does not require explicit `name` prop const TestConnected = (props: any) => ; } function TestWithoutTOwnPropsDecoratedInference() { interface ForwardedProps { forwarded: string; } interface OwnProps { own: string; } interface StateProps { state: string; } class WithoutOwnPropsComponentClass extends React.Component> { render() { return
; } } const WithoutOwnPropsComponentStateless: React.StatelessComponent> = () => (
); function mapStateToProps4(state: any, ownProps: OwnProps): StateProps { return { state: 'string' }; } // these decorations should compile, it is perfectly acceptable to receive props and ignore them const ConnectedWithOwnPropsClass = connect(mapStateToProps4)(WithoutOwnPropsComponentClass); const ConnectedWithOwnPropsStateless = connect(mapStateToProps4)(WithoutOwnPropsComponentStateless); const ConnectedWithTypeHintClass = connect(mapStateToProps4)(WithoutOwnPropsComponentClass); const ConnectedWithTypeHintStateless = connect(mapStateToProps4)(WithoutOwnPropsComponentStateless); // This should compile React.createElement(ConnectedWithOwnPropsClass, { own: 'string', forwarded: 'string' }); React.createElement(ConnectedWithOwnPropsClass, { own: 'string', forwarded: 'string' }); // This should not compile, it is missing ForwardedProps React.createElement(ConnectedWithOwnPropsClass, { own: 'string' }); // $ExpectError React.createElement(ConnectedWithOwnPropsStateless, { own: 'string' }); // $ExpectError // This should compile React.createElement(ConnectedWithOwnPropsClass, { own: 'string', forwarded: 'string' }); React.createElement(ConnectedWithOwnPropsStateless, { own: 'string', forwarded: 'string' }); // This should not compile, it is missing ForwardedProps React.createElement(ConnectedWithTypeHintClass, { own: 'string' }); // $ExpectError React.createElement(ConnectedWithTypeHintStateless, { own: 'string' }); // $ExpectError interface AllProps { own: string; state: string; } class AllPropsComponent extends React.Component> { render() { return
; } } type PickedOwnProps = Pick; type PickedStateProps = Pick; const mapStateToPropsForPicked: MapStateToProps = (state: any): PickedStateProps => { return { state: "string" }; }; const ConnectedWithPickedOwnProps = connect(mapStateToPropsForPicked)(AllPropsComponent); ; } // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/25321#issuecomment-387659500 function ProviderAcceptsStoreWithCustomAction() { const reducer: Reducer< { foo: number } | undefined, { type: "foo"; payload: number } > = state => state; const store = createStore(reducer); const Whatever = () => (
Whatever
); } function TestOptionalPropsMergedCorrectly() { interface OptionalDecorationProps { foo: string; bar: number; optionalProp?: boolean; dependsOnDispatch?: () => void; } class Component extends React.Component { render() { return
; } } interface MapStateProps { foo: string; bar: number; optionalProp: boolean; } interface MapDispatchProps { dependsOnDispatch: () => void; } function mapStateToProps(state: any): MapStateProps { return { foo: 'foo', bar: 42, optionalProp: true, }; } function mapDispatchToProps(dispatch: any): MapDispatchProps { return { dependsOnDispatch: () => { } }; } connect(mapStateToProps, mapDispatchToProps)(Component); } function TestMoreGeneralDecorationProps() { // connect() should support decoration props that are more permissive // than the injected props, as long as the injected props can satisfy // the decoration props. interface MoreGeneralDecorationProps { foo: string | number; bar: number | 'foo'; optionalProp?: boolean | object; dependsOnDispatch?: () => void; } class Component extends React.Component { render() { return
; } } interface MapStateProps { foo: string; bar: number; optionalProp: boolean; } interface MapDispatchProps { dependsOnDispatch: () => void; } function mapStateToProps(state: any): MapStateProps { return { foo: 'foo', bar: 42, optionalProp: true, }; } function mapDispatchToProps(dispatch: any): MapDispatchProps { return { dependsOnDispatch: () => { } }; } connect(mapStateToProps, mapDispatchToProps)(Component); } function TestFailsMoreSpecificInjectedProps() { interface MoreSpecificDecorationProps { foo: string; bar: number; dependsOnDispatch: () => void; } class Component extends React.Component { render() { return
; } } interface MapStateProps { foo: string | number; bar: number | 'foo'; dependsOnDispatch?: () => void; } interface MapDispatchProps { dependsOnDispatch?: () => void; } function mapStateToProps(state: any): MapStateProps { return { foo: 'foo', bar: 42, }; } function mapDispatchToProps(dispatch: any): MapDispatchProps { return { dependsOnDispatch: () => { } }; } // Since it is possible the injected props could fail to satisfy the decoration props, // the following line should fail to compile. connect(mapStateToProps, mapDispatchToProps)(Component); // $ExpectError // Confirm that this also fails with functional components const FunctionalComponent = (props: MoreSpecificDecorationProps) => null; connect(mapStateToProps, mapDispatchToProps)(Component); // $ExpectError } function TestLibraryManagedAttributes() { interface OwnProps { bar: number; fn: () => void; } interface MapStateProps { foo: string; } class Component extends React.Component { static defaultProps = { bar: 0, }; render() { return
; } } function mapStateToProps(state: any): MapStateProps { return { foo: 'foo', }; } const ConnectedComponent = connect(mapStateToProps)(Component); { }} />; const ConnectedComponent2 = connect(mapStateToProps)(Component); { }} />; } function TestNonReactStatics() { interface OwnProps { bar: number; } interface MapStateProps { foo: string; } class Component extends React.Component { static defaultProps = { bar: 0, }; static meaningOfLife = 42; render() { return
; } } function mapStateToProps(state: any): MapStateProps { return { foo: 'foo', }; } Component.meaningOfLife; Component.defaultProps.bar; const ConnectedComponent = connect(mapStateToProps)(Component); // This is a non-React static and should be hoisted as-is. ConnectedComponent.meaningOfLife; // This is a React static, so it's not hoisted. // However, ConnectedComponent is still a ComponentClass, which specifies `defaultProps` // as an optional static member. We can force an error (and assert that `defaultProps` // wasn't hoisted) by reaching into the `defaultProps` object without a null check. ConnectedComponent.defaultProps.bar; // $ExpectError } function TestProviderContext() { const store: Store = createStore((state = {}) => state); const nullContext = React.createContext(null); // To ensure type safety when consuming the context in an app, a null-context does not suffice. ; // $ExpectError // Providing a an object of the correct type ensures type safety when consuming the context. const initialContextValue: ReactReduxContextValue = { storeState: null, store }; const context = React.createContext(initialContextValue); ; // react-redux exports a default context used internally if none is supplied, used as shown below. class ComponentWithDefaultContext extends React.Component { static contextType = ReactReduxContext; } ; // Null is not a valid value for the context. ; // $ExpectError } function TestSelector() { interface OwnProps { key?: string; } interface State { key: string; } const simpleSelect: Selector = (state: State) => state.key; const notSimpleSelect: Selector = (state: State, ownProps: OwnProps) => ownProps.key || state.key; const ownProps = {}; const state = { key: 'value' }; simpleSelect(state); notSimpleSelect(state, ownProps); simpleSelect(state, ownProps); // $ExpectError notSimpleSelect(state); // $ExpectError } function testShallowEqual() { shallowEqual(); // $ExpectError shallowEqual('a'); // $ExpectError shallowEqual('a', 'a'); shallowEqual({ test: 'test' }, { test: 'test' }); shallowEqual({ test: 'test' }, 'a'); const x: boolean = shallowEqual('a', 'a'); } function testUseDispatch() { const actionCreator = (selected: boolean) => ({ type: "ACTION_CREATOR", payload: selected, }); const thunkActionCreator = (selected: boolean) => { return (dispatch: Dispatch) => { return dispatch(actionCreator(selected)); }; }; const dispatch = useDispatch(); dispatch(actionCreator(true)); dispatch(thunkActionCreator(true)); dispatch(true); type ThunkAction = (dispatch: Dispatch) => TReturnType; type ThunkDispatch = (action: ThunkAction) => TReturnType; // tslint:disable-next-line:no-unnecessary-callback-wrapper (required for the generic parameter) const useThunkDispatch = () => useDispatch(); const thunkDispatch = useThunkDispatch(); const result: ReturnType = thunkDispatch(thunkActionCreator(true)); } function testUseSelector() { interface State { counter: number; active: string; } const selector = (state: State) => { return { counter: state.counter, active: state.active, }; }; const { counter, active } = useSelector(selector); counter === 1; counter === "321"; // $ExpectError active === "hi"; active === {}; // $ExpectError const { extraneous } = useSelector(selector); // $ExpectError useSelector(selector); useSelector(selector, 'a'); // $ExpectError useSelector(selector, (l, r) => l === r); useSelector(selector, (l, r) => { // $ExpectType { counter: number; active: string; } l; return l === r; }); const correctlyInferred: State = useSelector(selector, shallowEqual); const inferredTypeIsNotString: string = useSelector(selector, shallowEqual); // $ExpectError const compare = (_l: number, _r: number) => true; useSelector(() => 1, compare); const compare2 = (_l: number, _r: string) => true; useSelector(() => 1, compare2); // $ExpectError interface RootState { property: string; } const useTypedSelector: TypedUseSelectorHook = useSelector; // $ExpectType string const r = useTypedSelector(state => { // $ExpectType RootState state; return state.property; }); } function testUseStore() { interface TypedState { counter: number; active: boolean; } interface TypedAction { type: "SET_STATE"; } const untypedStore = useStore(); const state = untypedStore.getState(); state.things.stuff.anything; // any by default const typedStore = useStore(); const typedState = typedStore.getState(); typedState.counter; typedState.things.stuff; // $ExpectError } // These should match the types of the hooks. function testCreateHookFunctions() { // $ExpectType { >(): TDispatch; = AnyAction>(): Dispatch; } createDispatchHook(); // $ExpectType (selector: (state: TState) => TSelected, equalityFn?: ((left: TSelected, right: TSelected) => boolean) | undefined) => TSelected createSelectorHook(); interface RootState { property: string; } // Should be able to create a version typed for a specific root state. const useTypedSelector: TypedUseSelectorHook = createSelectorHook(); // $ExpectType = AnyAction>() => Store createStoreHook(); } function testConnectedProps() { interface OwnProps { own: string; } const Component: React.FC = ({ own, dispatch }) => null; const connector = connect(); type ReduxProps = ConnectedProps; const ConnectedComponent = connect(Component); } function testConnectedPropsWithState() { interface OwnProps { own: string; } const Component: React.FC = ({ own, injected, dispatch }) => { injected.slice(); return null; }; const connector = connect((state: any) => ({ injected: '' })); type ReduxProps = ConnectedProps; const ConnectedComponent = connect(Component); } function testConnectedPropsWithStateAndActions() { interface OwnProps { own: string; } const actionCreator = () => ({ type: 'action' }); const Component: React.FC = ({ own, injected, actionCreator }) => { actionCreator(); return null; }; const ComponentWithDispatch: React.FC = ({ own, dispatch }) => null; // $ExpectError const connector = connect( (state: any) => ({ injected: '' }), { actionCreator } ); type ReduxProps = ConnectedProps; const ConnectedComponent = connect(Component); } function testConnectReturnType() { const TestComponent: React.FC = () => null; const Test = connect()(TestComponent); const myHoc1 = (C: React.ComponentClass

): React.ComponentType

=> C; myHoc1(Test); // $ExpectError const myHoc2 = (C: React.FC

): React.ComponentType

=> C; myHoc2(Test); }