Reputation: 4575
I try to build a react app in typescript using redux and react-router-dom. I ran into typing issues when I added redux to my app. Thus I created the following minimal example with only one page test-page:
App.jsx
import * as React from 'react';
import { Route, Redirect } from 'react-router-dom'
import Test from './containers/test-page'
import './App.css';
class App extends React.Component {
render() {
return (
<div className="ui container" id="main">
<Route exact path="/" render={() => <Redirect to="/test" />}/>
<Route exact path="/test" component={Test} />
</div>
);
}
}
export default App;
The container for the test page looks like this. It produces a typing error in the call to connect.
containers/test-page/index.tsx
import { Dispatch } from 'redux'
import { connect } from 'react-redux'
import TestPage from './test-page'
function mapDispatchToProps(dispatch: Dispatch<any>) {
return dispatch({ type: 'ALERT_USER' });
}
function mapStateToProps(state: any) {
return { label: 'my test label' }
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TestPage)
The container uses the following react component, which in production should render a page for the router. It produces two errors, see below.
containers/test-page/test-page.tsx
import * as React from 'react';
export namespace Test {
export interface Props {
alert: () => void;
label: string;
}
export interface State {
}
}
export default class TestPage extends React.Component {
constructor(props?: Test.Props, state?: Test.State, context?: any) {
super(props, context);
}
sendAlert = () => {
this.props.alert()
}
render() {
return (
<div>
<h1>Test</h1>
<button onClick={this.sendAlert}>{this.props.label}</button>
</div>
);
}
}
proxyConsole.js:54 ./src/containers/test-page/test-page.tsx
(20,18): error TS2339: Property 'alert' does not exist on type 'Readonly<{ children?: ReactNode; }> & Readonly<{}>'.
proxyConsole.js:54 ./src/containers/test-page/test-page.tsx
(27,54): error TS2339: Property 'label' does not exist on type 'Readonly<{ children?: ReactNode; }> & Readonly<{}>'.
proxyConsole.js:54 ./src/containers/test-page/index.tsx
(16,3): error TS2345: Argument of type 'typeof TestPage' is not assignable to parameter of type 'ComponentType<{ label: string; } & { type: string; }>'.
Type 'typeof TestPage' is not assignable to type 'StatelessComponent<{ label: string; } & { type: string; }>'.
Type 'typeof TestPage' provides no match for the signature '(props: { label: string; } & { type: string; } & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.
I tried to follow different guides and looked up example implementations but could not solve these issues. I do not understand the error messages of the typescript compiler:
this.props
when I defined them?Upvotes: 9
Views: 33018
Reputation: 6705
Working syntax variant for Type Script application is:
import * as React from 'react';
import { connect } from 'react-redux';
interface ComponentProps {
// Your properties here
}
interface ComponentState {
// Your properties here
}
interface MapStateToPropsTypes {
// Your properties here
}
interface MapDispatchToPropsTypes {
// Your properties here
}
class MyComponentName extends React.Component<ComponentProps, ComponentState> {
constructor(props: ComponentProps) {
super(props);
}
}
export default connect<MapStateToPropsTypes, MapDispatchToPropsTypes>(
mapStateToProps,
mapDispatchToProps)
(MyComponentName)
Upvotes: 1
Reputation: 1997
This doesn't work because connect is a generic function. This means you need to provide additional type parameters.
connect<StateProps, DispatchProps>({
mapStateToProps,
mapDispatchToProps,
})(SomeComponent);
You can find typing implementation here. Everything you need to know is there C:
Upvotes: 12
Reputation: 1010
A couple of things I notice:
1) As far as I've seen in examples and when working with props
in TypeScript, your call to React.Component
needs to specify Props
as a type argument like so:
export default class TestPage extends React.Component<Test.Props, Test.State>{
constructor(props: Test.Props) {
super(props);
}
}
You can specify that your component does not accept props
or state
by passing empty interfaces i.e.:
export default class TestPage extends React.Component<{}, {}>{
// constructor becomes 'useless' at this point and is not needed
constructor() {
super();
}
}
I think this explains why your call signature is not matching and why there are no properties visible on this.props
- TS sees an interface of ReadOnly{}
since it has no type arguments passed in.
2) Your mapStateToProps
function doesn't look quite right. mapStateToProps
takes two arguments, state
(referencing your Redux store
) and ownProps
as an optional second argument, which refers to props
passed down from the parent. So mapStateToProps
should look like this:
function mapStateToProps(state: any, ownProps: { label: string }) {
return {
label: ownProps.label
};
}
This is my guess for why connect
is throwing an error - it is simply a place where you make assertions about how Redux should handle combining props
coming from the store
and props
coming from the parent. Let me know if this works out.
Upvotes: 16