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.
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.
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.
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.
TypeScript supports embedding, type checking, and compiling JSX directly to JavaScript.
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With