Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type Error when using Typescript with React-Redux

I am trying to use react-redux with typescript and I'm getting a type error when I try to inject props using connect() and mapStateToProps.

My component looks like this:

function mapStateToProps(state) {
    return {
        counter: state.counter
    };
}

function mapDispatchToProps(dispatch) {
    return {
        incr: () => {
            dispatch({type: 'INCR', by: 2});
        },
        decr: () => {
            dispatch({type: 'INCR', by: -1});
        }
    };
}




export default class Counter extends React.Component<HelloProps, any> {
    render() {
        return (
          <div>
              <p>
                  <label>Counter: </label>
                  <b>#{this.props.counter}</b>
              </p>
              <button onClick={e => this.props.incr() }>INCREMENT</button>
              <span style={{ padding: "0 5px" }}/>
              <button onClick={e => this.props.decr() }>DECREMENT</button>
        );
    }
}


export default connect(mapStateToProps, mapDispatchToProps)(Counter);

The store looks like this

let store = createStore(
    (state:HelloState, action:HelloAction) => {
        switch (action.type) {
            case 'INCR':
                return {counter: state.counter + action.by};
            default:
                return state;
        }
    },

Finally, I have defined my types to be:

interface HelloProps {
    counter?: number;
    incr?: () => any;
    decr?: () => any;
}

interface HelloState { 
    counter:number;
}

interface HelloAction { 
    type:string, 
    by:number;
}

When I try and compile the code I get the following error:

(39,61): error TS2345: Argument of type 'typeof Counter' is not assignable to parameter of type 'ComponentClass<{ counter: any; } & { incr: () => void; decr: () => void; }> | StatelessComponent<...'.
  Type 'typeof Counter' is not assignable to type 'StatelessComponent<{ counter: any; } & { incr: () => void; decr: () => void; }>'.
    Type 'typeof Counter' provides no match for the signature '(props?: { counter: any; } & { incr: () => void; decr: () => void; }, context?: any): ReactElement<any>' 

Interestingly the code still works even though it throws the type error. Also, changing the component's prop interface to any also solves the issue. It seems like the type system doesn't understand that the two objects are merged by the two mapped functions.

like image 368
Ochowie Avatar asked May 03 '16 17:05

Ochowie


People also ask

Can you use Redux with TypeScript?

We strongly recommend using TypeScript in Redux applications. However, like all tools, TypeScript has tradeoffs. It adds complexity in terms of writing additional code, understanding TS syntax, and building the application.

Should I use TypeScript with React Redux?

Redux can offer a better developer experience when you use it along with TypeScript. TypeScript is a superset of JavaScript that type-checks code to make it robust and understandable.

What is the type of Redux dispatch?

Redux doesn't have a Dispatcher or support many stores. Instead, there is just a single store with a single root reducing function. As your app grows, instead of adding stores, you split the root reducer into smaller reducers independently operating on the different parts of the state tree.

Can you use TypeScript in JSX?

TypeScript supports embedding, type checking, and compiling JSX directly to JavaScript.


2 Answers

To preserve type safety you can divide your props in parts, one responsible for normal props and one for dispatchers:

import * as React from 'react';
import {connect}  from 'react-redux';

interface StateProps {
    textPros: string,
    optionalText?: string,

}
interface DispatchProps {
    onClick1: Function,

}

class MyComp extends React.Component<StateProps & DispatchProps , any> {
    render() {
         return (<div onClick={this.props.onClick1}>{this.props.textPros}</div>);
    }
}
const mapStateToProps = (state: any, ownProp? :any):StateProps  => ({
    textPros: "example text",
});
const mapDispatchToProps = (dispatch: any):DispatchProps => ({
    onClick1: () => {
        dispatch({ type: 'CLICK_ACTION'});
    }
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComp);

For those who search for quick workaround: just add 'as any' to base component.

export default connect(mapStateToProps, mapDispatchToProps)(MyComp as any);
like image 23
vinga Avatar answered Sep 29 '22 14:09

vinga


I found the answer in the second to last post on this Github issue. Without the type parameter on both the mapStateToProps and/or mapDispatchToProps or on connect it will produce an intersection of the return types of the two map functions.

like image 67
Ochowie Avatar answered Sep 29 '22 14:09

Ochowie