2

I'm using typescript in an application, but found some problem with react-redux. The 'connect' method reports a problem and I have no idea with it since I'm a rookie with typescript and redux. What should I do or where in my code should be modified? Thanks a lot

Application built with [email protected], [email protected], [email protected].

// article
interface IArticle {
  title: string,
  author: string
}

// state
interface MyState {
  list: [],
  article: IArticle
}

// stateMapToProps
interface MyStateProps {
  article: IArticle
}

// router match
interface MatchParams {
  id: string
}

// own props
interface MyOwnProps extends RouteComponentProps < MatchParams > {
  article: IArticle,
  dispatch: (o: object) => {}
}

class ArticleContainer extends Component < MyOwnProps, {} > {
  constructor(props: MyOwnProps) {
    super(props);
  }

  componentDidMount() {
    const {
      dispatch
    } = this.props;
    const id = this.props.match.params.id
    dispatch(fetchArticle(id))
  }

  render() {
    const {
      article
    } = this.props;
    return ( <
      Article article = {
        article
      } > < /Article>
    )
  }
}

const mapStateToProps = (state: MyState): MyStateProps => {
  return {
    article: state.article
  }
}

export default connect < MyStateProps, {}, {
  article: IArticle
} > (
  mapStateToProps
)(ArticleContainer)

Here is the code of async action fetchArticle

function fetchArticle(id: string) {
  return function(dispatch: (action: AnyAction) => {}): Promise<void> {
    dispatch(getArticle(id))

    return axios.get(`/article/${id}`)
      .then(res => {
        dispatch(getArticleSuccess(res.data))
      })
  }
}

Error happens at the export line and message is as below:

Argument of type '(state: MyState) => MyStateProps' is not assignable to parameter of type 'MapStateToPropsParam'. Type '(state: MyState) => MyStateProps' is not assignable to type 'MapStateToPropsFactory'. Types of parameters 'state' and 'initialState' are incompatible. Type '{}' is missing the following properties from type 'MyState': list, articlets(2345)

5
  • Just remove the typing from connect. Connect is already infers typing, from mapStateToProps. export default connect(mapStateToProps)(ArticleContainer); Commented Jul 10, 2019 at 5:44
  • also, your component should be Component<MyOwnProps, MyState> Commented Jul 10, 2019 at 5:45
  • recommend checking out the following resource Commented Jul 10, 2019 at 5:46
  • @eenagy thanks first. I tried and instead the ArticleContainer part in export line report a message, also the interface MyState represents the structure of the store created with redux, not the 'state' of the Class, so I think there is no need to put it in the generics. I hope I make myself understandable. Commented Jul 10, 2019 at 6:00
  • And the error message for ArticleContainer in connect part is as below(only record the first sentence since it is too long to write here): Argument of type 'typeof ArticleContainer' is not assignable to parameter of type 'ComponentType<Matching<MyStateProps & DispatchProp<AnyAction>, MyOwnProps>>'......ts(2345) Commented Jul 10, 2019 at 6:01

3 Answers 3

3

Minimum steps to be able to compile your code:

  1. MyOwnProps should be

    import { AnyAction } from 'redux';
    
    interface AppThunkAction<TAction> {
        (dispatch: (action: TAction) => void, getState: () => MyState): any;
    }
    
    // As you're going to dispatch thunk actions, dispatch should be overloaded
    interface Dispatch<TAction> {
        (action: AppThunkAction<TAction>): any
        (action: TAction): TAction
    }
    
    // own props
    interface MyOwnProps extends RouteComponentProps<MatchParams> {
        article: IArticle,
        dispatch: Dispatch<AnyAction>
    }
    
  2. If you want to provide types for connect function, add MyState as last type like so

    export default connect <MyStateProps, {}, {
            article: IArticle
        }, MyState >(
            mapStateToProps
    )(ArticleContainer)
    

    Or you can allow compiler to infer types itself, which is preferred

    export default connect(
        mapStateToProps
    )(ArticleContainer)
    

So working result

import { Component } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { connect, ResolveThunks } from 'react-redux';
import { AnyAction } from 'redux';
import axios from 'axios';

// article
interface IArticle {
    title: string,
    author: string
}

// state
interface MyState {
    list: [],
    article: IArticle
}

// stateMapToProps
interface MyStateProps {
    article: IArticle
}

// router match
interface MatchParams {
    id: string
}

export interface AppThunkAction<TAction> {
    (dispatch: (action: TAction) => void, getState: () => MyState): any;
}

interface Dispatch<TAction> {
    (action: AppThunkAction<TAction>): any
    (action: TAction): TAction
}

// own props
interface MyOwnProps extends RouteComponentProps<MatchParams> {
    article: IArticle,
    dispatch: Dispatch<AnyAction>
}

function getArticle(id: string) {
    return {
        type: 'GET_ARTICLE',
        id
    }
}

function getArticleSuccess(i: any) {
    return {
        type: 'SET_ARTICLE',
        i
    }
}



const fetchArticle = (id: string): AppThunkAction<AnyAction> =>
    (dispatch, getState) => {
        dispatch(getArticle(id))

        return axios.get(`/article/${id}`)
            .then(res => {
                dispatch(getArticleSuccess(res.data))
            })
    }

class ArticleContainer extends Component<MyOwnProps, {}> {
    constructor(props: MyOwnProps) {
        super(props);
    }

    componentDidMount() {
        const {
            dispatch
        } = this.props;
        const id = this.props.match.params.id
        dispatch(fetchArticle(id))
    }

    render() {
        const {
            article
        } = this.props;
        return (<div>article: {article}</div>
          )
        }
      }

const mapStateToProps = (state: MyState): MyStateProps => {
  return {
                article: state.article
          }
        }

export default connect(
            mapStateToProps
)(ArticleContainer)
Sign up to request clarification or add additional context in comments.

4 Comments

thanks for your help. This method solves the error I mentioned, however, the dispatch(fetchArticle(id)) reports an error, is it because my fetchArticle method is a async method and returns a Promise? ps: I supplement fetchArticle code in the question.
I think the error mainly reports the parameter type, but I have no idea how to solve it. Here is the error message: Argument of type '(dispatch: (action: AnyAction) => {}) => Promise<void>' is not assignable to parameter of type 'AnyAction'. Property 'type' is missing in type '(dispatch: (action: AnyAction) => {}) => Promise<void>' but required in type 'AnyAction'.ts(2345)
I have already solved the problem based on your method, thanks a lot for your help!
Sorry, doesn;t mentionet this. I've updated the answer and typed dispatch so it be able to take functions as arguments
0

Here is a working example, how to type a react file when using redux, based on your example.

// article-container.js file
import { connect, DispatchProp } from "react-redux";
import { Component } from "react";

// import the real RouteComponent, this is just a demo example
interface RouteComponentProps<T> {
  match: { params: T };
}
// article
interface IArticle {
  title: string;
  author: string;
}
// import the real Article, this is just a demo example

const Article = ({ article }: { article: IArticle }) => {
  return <div>{article.title}</div>;
};

// import the real fetchArticle, this is just a demo example

const fetchArticle = (id: string) => {
  return {
    type: "SOME_ACTION",
    payload: {
      id,
    },
  };
};

// state
interface MyState {
  list: [];
  article: IArticle;
}

// stateMapToProps
interface MyStateProps {
  article: IArticle;
}

// router match
interface MatchParams {
  id: string;
}

// own props
interface MyOwnProps {
  article: IArticle;
}
type AllProps = MyOwnProps & RouteComponentProps<MatchParams> & DispatchProp;

class ArticleContainer extends Component<AllProps> {
  constructor(props: AllProps) {
    super(props);
  }

  componentDidMount() {
    const { dispatch } = this.props;
    const id = this.props.match.params.id;
    dispatch(fetchArticle(id));
  }

  render() {
    const { article } = this.props;
    return <Article article={article} />;
  }
}

const mapStateToProps = (state: MyState): MyStateProps => {
  return {
    article: state.article,
  };
};

export default connect(
  mapStateToProps
)(ArticleContainer);

And use it like so


import ArticleContainer from "./article-container";

export default () => {
  // this is coming from router, just an example for demonstration
  const match = { params: { id: "23" } };
  return (
    <div>
      <ArticleContainer match={match} />
    </div>
  );
};

1 Comment

Thanks for your help, I solved the problem based on this.
0

Finally solved with remove the generics declaration of connect method and use ThunkDispatch with async action creator. The code is below.

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ThunkDispatch } from 'redux-thunk';
import { fetchArticle } from '../store/actions';
import { RouteComponentProps } from 'react-router-dom';
import Article from '../components/Article/Article'
import { AnyAction } from 'redux';

// article
interface IArticle {
  title: string,
  author: string
}

// state
interface MyState {
  list: [],
  article: IArticle
}

// stateMapToProps
interface StateToProps {
  article: IArticle
}

// router match
interface MatchParams {
  id: string
}

// own props
interface MyOwnProps extends RouteComponentProps<MatchParams> {
  article: IArticle,
  getArticle: (id: string) => Promise<void>
}

class ArticleContainer extends Component<MyOwnProps, {}> {
  constructor(props: MyOwnProps) {
    super(props);
  }

  componentDidMount() {
    const { getArticle } = this.props;
    const id = this.props.match.params.id
    getArticle(id)
  }

  render() {
    const { article } = this.props;
    return (
      <Article article={article}></Article>
    )
  }
}

const mapStateToProps = (state: MyState): StateToProps => {
  return {
    article: state.article
  }
}

const mapDispatchToProps = (dispatch: ThunkDispatch<any, any, AnyAction>) => {
  return {
    getArticle: (id: string) => dispatch(fetchArticle(id))
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(ArticleContainer)

Thanks for the help!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.