Definitelytyped: react-redux - connect does not work

Created on 10 Oct 2015  ·  33Comments  ·  Source: DefinitelyTyped/DefinitelyTyped

For example (in App.tsx)

export default connect(mapStateToProps)(App);

and then:

import App from './App';
...
    <Provider store={store}>
        {() =>
                <App />
            }
    </Provider>

causes compile error: (TypeScript 1.6)

Error TS2604: JSX element type 'App' does not have any construct or call signatures.

If I change the type definition so that it returns a Function:

export function connect(mapStateToProps?: MapStateToProps,
                      mapDispatchToProps?: MapDispatchToPropsFunction|MapDispatchToPropsObject,
                      mergeProps?: MergeProps,
                      options?: Options): Function;

it compiles and works.

Most helpful comment

I think the generic type parameter you've given is not correct. Depending on how you want to pass progress prop to the Component, connect type signature will differ.

Case 1. You want to directly pass progress value to the component, not relying on react-redux

1.1: provide type parameter explicitly on connect

type MyViewPropType = {
  progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any): {} {
  return {};
}
const ConnectedMyView = connect<{}, {}, MyViewPropType>(mapStateToProps)(MyView);
let myViewElement = <ConnectedMyView progress={42} />;

1.2: don't provide type parameter on connect

In this case, you have to provide type parameter on mapStateToProps instead, otherwise connect doesn't know how to construct MyView's MyViewPropType from state props, dispatch props, and own props

type MyViewPropType = {
  progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any, ownProp?: MyViewPropType): {} {
  return {};
}
const ConnectedMyView = connect(mapStateToProps)(MyView);
let myViewElement = <ConnectedMyView progress={42} />;

Case 2. You want react-redux to provide progress value via mapStateToProps to the component

type MyViewPropType = {
  progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any): MyViewPropType {
  return { progress: 42 };
}
// here connect's type parameters aren't necessarily required
const ConnectedMyView = connect<MyViewPropType, {}, {}>(mapStateToProps)(MyView);
let myViewElement = <ConnectedMyView />;

Case 3. You want react-redux to provide progress value via mapDispatchToProps to the component

type MyViewPropType = {
  progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any): {} {
  return {};
}
function mapDispatchToProps(dispatch: Redux.Dispatch): MyViewPropType {
  return { progress: 42 };
}
// here connect's type parameters aren't necessarily required
const ConnectedMyView = connect<{}, MyViewPropType, {}>(mapStateToProps, mapDispatchToProps)(MyView);
let myViewElement = <ConnectedMyView />;

Four examples above do compile in my environment. I hope this will help you.

All 33 comments

I think that the problem is with the definition of ClassDecorator. Right now it looks like that:

 <TFunction extends Function>(target: TFunction): TFunction|void;

and that one does not work because of that void. That one is correct:

 <TFunction extends Function>(target: TFunction): TFunction;

It fixes the problem with injecting into JSX.
What do you think @tkqubo ?

@niba Actually the definition of ClassDedorator is loose (I simply copy-and-pasted it from lib.d.ts) and I totally agree with your proposal.

And if this decorator is to be used only on react Component, the definition could be more specific. Or should it be?

Anyway this time I will fix to the simple definition you gave

 <TFunction extends Function>(target: TFunction): TFunction;

Thanks @niba @schlaup !

I was thinking about

<T extends Component<any,any>> 

but in this case typescript compiler will expect an instance of class and in React case we dont wan't to create a w new instance of App/Component

Hi @niba
I just came up with this code snippet and it can somehow compile:

import * as React from 'react';
import { Component } from 'react';
class ComponentInterface extends Component<any, any> { }
declare function componentWrapper<T extends (typeof ComponentInterface)>(component: T): T;

// below are the sample code

interface Prop {
  property1: number;
  someOtherProperty?: string;
}
interface State {
  isLoaded: boolean;
  state1: number;
}

class TestComponent extends Component<Prop, State> { }
let WrappedTestComponent = componentWrapper(TestComponent);

// can compile!
let component = <TestComponent property1={1} />;
component = <WrappedTestComponent property1={1} />;

function foo() { }
class NotComponent { }

// compile error!
//componentWrapper(NotComponent);
// compile error!
//componentWrapper(foo);

Maybe I can apply this componentWrapper for connect decorator :smile:

Hi @tkqubo
It can compile because of this empty body of ComponentInterface, when you add something to the interface it will stop compile.
Generally I like it more than the solution with TFunction. With this we have some information what we should pass to the connect method.
Good job!

ComponentInterface is meant to be a kind of workaround since TypeScript doesn't allow generics like below:

// this is what I actually want to do, but doesn't compile
<T extends (typeof Component<any,any>)>

So it is not supposed to add something to it. You just need to make your class extend Component<P, S> to make this componentWrapper work

Thanks for your comment @niba

connect does not work with StatelessComponent from 0.14. It should accept Component or StatelessComponent (which is just a function).

I think TypeScript 1.7 itself doesn't seem to support stateless component yet. https://github.com/Microsoft/TypeScript/issues/5478

So connect should be modified after 1.8 has been released.

https://github.com/Microsoft/TypeScript/wiki/Roadmap

Seems the issue is for JSX/TSX. I'm not using that, just regular functions.

Just ran into this issue. Is a fix good to get applied now that TypeScript is at 1.8.2?

@jschaf sure, i also think so. i was preparing for stateless component. I'll make a PR soon

@tkqubo keep us posted on that PR!

I made a pr for react-redux update for stateless component #8323

it merged. solved this issue?

@vvakame thanks for reminding. I think this issue can be closed if no further objections

For some reason I still get this error with the latest typing.

Here is the code:

interface CounterProps {
    count: number;
    onClick: any;
}

const Counter: React.StatelessComponent<CounterProps> = ({count, onClick}) => (
    <div>
        <h1>Counter below</h1>
        <p onClick={onClick}>{count}</p>
    </div>
);

Counter.propTypes = {
    count: React.PropTypes.number.isRequired,
    onClick: React.PropTypes.func.isRequired
};

const mapStateToProps = (state: ICounter) => {
    return { count: state.counter };
};

const mapDispatchToProps = (dispatch) => {
    return { onClick: dispatch(increaseCounter) };
};

const CounterComponent = connect(mapStateToProps, mapDispatchToProps)(Counter);

The error I am getting:

src/app.tsx(50,71): error TS2345: Argument of type 'StatelessComponent' is not assignable to parameter of type 'ComponentClass<{ count: number; } & { onClick: any; }>'.
Type 'StatelessComponent' provides no match for the signature 'new (props?: { count: number; } & { onClick: any; }, context?: any): Component<{ count: number; } & { onClick: any; }, {}>'

changed definition to

function createStore(reducer: Reducer, initialState?: any, enhancer?: Function): Store;

for now

I get the same as @alexeyzimarev. Ping @tkqubo

Sorry I missed the comments! pong :rocket:

I think changing the ComponentDecorator definition might help.

// before
interface ComponentDecorator<TOriginalProps extends Props<any>, TOwnProps extends Props<any>> {
  (component: ComponentClass<TOriginalProps>): ComponentClass<TOwnProps>;
}
// after
interface ComponentDecorator<TOriginalProps extends Props<any>, TOwnProps extends Props<any>> {
  (component: ComponentClass<TOriginalProps>|StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>;
}

@use-strict sorry for bothering you, but do you think this modification is appropriate? if so I will create a PR for that.

But for now, casting Counter to any will make your lines of code pass compilation. @alexeyzimarev

const CounterComponent = connect(mapStateToProps, mapDispatchToProps)(Counter as any);

@tkqubo , can't be 100% sure, but it looks OK. In the end, the tests should pass. You should also add this new example to the test file.

If I have a component that accepts some props, let's say

type MyViewPropType = {
    progress: number
}

class MyView extends React.Component<MyViewPropType, any> {
}

And I want the connected version to retain typing information, so I did

connect<MyView>(mapStateToProps)(MyView)

But the I got Type 'MyView' does not satisfy the constraint 'Function'. Property 'apply' is missing in type 'MyView'.

What is the proper way to do this?

I think the generic type parameter you've given is not correct. Depending on how you want to pass progress prop to the Component, connect type signature will differ.

Case 1. You want to directly pass progress value to the component, not relying on react-redux

1.1: provide type parameter explicitly on connect

type MyViewPropType = {
  progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any): {} {
  return {};
}
const ConnectedMyView = connect<{}, {}, MyViewPropType>(mapStateToProps)(MyView);
let myViewElement = <ConnectedMyView progress={42} />;

1.2: don't provide type parameter on connect

In this case, you have to provide type parameter on mapStateToProps instead, otherwise connect doesn't know how to construct MyView's MyViewPropType from state props, dispatch props, and own props

type MyViewPropType = {
  progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any, ownProp?: MyViewPropType): {} {
  return {};
}
const ConnectedMyView = connect(mapStateToProps)(MyView);
let myViewElement = <ConnectedMyView progress={42} />;

Case 2. You want react-redux to provide progress value via mapStateToProps to the component

type MyViewPropType = {
  progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any): MyViewPropType {
  return { progress: 42 };
}
// here connect's type parameters aren't necessarily required
const ConnectedMyView = connect<MyViewPropType, {}, {}>(mapStateToProps)(MyView);
let myViewElement = <ConnectedMyView />;

Case 3. You want react-redux to provide progress value via mapDispatchToProps to the component

type MyViewPropType = {
  progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any): {} {
  return {};
}
function mapDispatchToProps(dispatch: Redux.Dispatch): MyViewPropType {
  return { progress: 42 };
}
// here connect's type parameters aren't necessarily required
const ConnectedMyView = connect<{}, MyViewPropType, {}>(mapStateToProps, mapDispatchToProps)(MyView);
let myViewElement = <ConnectedMyView />;

Four examples above do compile in my environment. I hope this will help you.

@tkqubo Thank you very much! That's very detailed and helpful.

@tkqubo Thanks for posting this as I came across this similar issue. I'm new to Typescript but I'm wondering if there's a way for connect to produce a combination of the inferred types of mapStateToProps and mapDispatchToProps rather than an intersection type. It seems like that would solve the issue but I'm not sure if it's possible.

@ochowie Sorry to be late. I need to know what you want to do more in detail. Could you show us example lines regardless of they compile or not?

I'm still new to TS so this might not be possible but what I wasn't sure of is why the following code

mapStateToProps = (state) => ({
    test: state.testString
 })

mapDispatchToProps = (dispatch) =. ({
   incr: (field) => {
            dispatch({type: 'TEST', field});
  })

connect(mapStateToProps, mapDispatchToProps)(Component)

produces an intersection type of the two return objects rather than a combination. Is it because the TS compiler thinks this is a more "general" way of describing the types? I would expect it to return a type of
{ test: String, incr: (string)=>{}) but instead it produces {test: String} & {incr: (string) =>{}}

Edit: I know based on your earlier example that I can make this work by adding generic types to connect or specifying a return type on the two map functions. However, I was hoping to better understand why it was necessary. Also, if I went with the second approach (specifying the return type on the two map functions) then the members of the return type would have to be optional otherwise it would fail to build, correct?

Removing the will result in the error which is topic of this issue.

export const LoginForm = connect(mapStateToProps, mapDispatchToProps)(reduxForm({form: 'loginForm', validate})(Login));

is this related?

Any good examples of a work around of this? There are tons of issues listed here, but no solid workarounds/solutions

I was so excited about TypeScript until I hit this error...

Still waiting for fix on this, it hasn't been addressed yet. Re Writing these components in JSX for now, pulling them away from TS until react-redux and TS learn to live together better!

@evanjmg @savovs @stevematdavies
I will show you how I work with the connect, maybe that will help you.

First, in every project, I do module augmentation like that

import { Component, ComponentClass } from "react-redux";

declare module "react-redux" {
  export interface InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> {
    <P extends TInjectedProps>(component: Component<P>): ComponentClass<Omit<P, keyof TInjectedProps> & TNeedsProps> & {
      WrappedComponent: Component<P>;
    };

    allProps: TInjectedProps & TNeedsProps;
  }
}

this code adds allProps property, it will only be used by the typescript compiler to satisfy typings so it's not important that it doesn't exist in a js code

Now the example of container

import * as React from "react";
import { connect } from "react-redux";
import { ApplicationState } from "modules/app.reducers";

interface ITestContainerProps {
 externalProp: number;
}

class TestContainer extends React.Component<ITestContainerProps & IConnectProps, any> {
  render() {
    this.props.testAction(); // typings ok
    return (
      <div>
        {this.props.count /* typings works  */}

      </div>
    );
  }
}

const connectCreator = connect(
  (state: ApplicationState) => ({
    count: state.count,
  }),
  {
    testAction: () => {},
  },
);

type IConnectProps = typeof connectCreator.allProps;

export default connectCreator(TestContainer);

<TestContainer externalProp={5} /> // typings ok

definition of connect props is automatically generated by using that magic allProps property that we added earlier. If you want to add any custom types that you don't pass from mapStateToProps and mapDspatchToProps you extend Props interface above the container

That's interesting, very much appreciated.
However, considering the amount of elements in our service, and the wide range of different props, state and redux-state, this is not a viable solution. In addition this seems a little overly complex, particularly if using composed components. This also doesn't seem to quite address the issue. Ill try and look into your concept more out of Interest.

Many Thanks

This seems to be an issue with the React typings, not just react-redux. I see similar issues on other components after updating my types.

Was this page helpful?
0 / 5 - 0 ratings