Search code examples
javascriptreactjsreduxreact-reduxredux-thunk

How to configure mapDispatchToProps with react redux?


Below is my container code:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { fetchMovies } from './actions';

export class Container extends Component {
  componentDidMount() {
    this.props.fetchMovies();
  }
}

const mapStateToProps = state => {
  return {
    movies: state.movies,
  };
};

function mapDispatchToProps(dispatch) {
  return {
    fetchMovies: bindActionCreators({fetchMovies}, dispatch),
  };
}

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

Action:

export const fetchMovies = () => dispatch => {
  console.log('fetchMovies called');
  // const movieResponse = fetchAPI(apiUrl);
  // console.log('movieResponse => ', JSON.stringify(movieResponse));
  dispatch({ type: actionTypes.FETCH_MOVIES, payload: [] });
};

Store:

import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from '../rootReducer';

const middlewares = [thunk];

const store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(...middlewares))
);

export default store;

Dependency Versions:

"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-bootstrap": "^1.0.0-beta.11",
"react-dom": "^16.8.6",
"react-flexbox-grid": "^2.1.2",
"react-redux": "^7.1.0",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"react-scripts": "3.0.1",
"redux": "^4.0.4",
"redux-actions": "^2.6.5",
"redux-promise-middleware": "^6.1.1",
"redux-thunk": "^2.3.0",

Getting error:

TypeError: this.props.fetchMovies is not a function

I am using latest react version of 16.8. Is that the reason? or am i missing something in configuration?


Solution

  • 🎈 It is much more simple.

    I don't know why you are using "thunk", I would suggest "saga" instead. I don't like the idea of adding some logic (fetching) to the actions, but any way here is your solution.

    Container:

    import React, { Component } from "react";
    import connect from "./connect";
    
    export class MovieList extends Component {
      componentDidMount() {
        this.props.fetchMovies();
      }
    
      render() {
        const { isFetching, movies } = this.props;
        const toLi = (movie, key) => <li key={key}>{movie.title}</li>;
    
        return isFetching
          ? <span>Data is loading</span>
          : <ul>{movies.map(toLi)}</ul>
      }
    }
    
    export default connect(MovieList); // pay attention to this
    
    

    Binding container with store, this way will keep your container much like component:

    import { connect } from "react-redux";
    import { fetchMovies } from "../actions";
    
    const props = state => ({
      movies: state.movies,
      isFetching: state.isFetching
    });
    
    const actions = {
      fetchMovies
    };
    
    export default connect(
      props,
      actions
    );
    
    

    Actions file:

    export const FETCH_MOVIES = `MOVIE/FETCH_MOVIES`;
    export const FETCHED_MOVIES = `MOVIE/FETCHED_MOVIES`;
    export const FETCH_ERROR = `MOVIE/FETCH_ERROR`;
    
    export const receivedMovies = movies => ({
      type: FETCHED_MOVIES,
      movies
    });
    
    export const onError = error => ({
      type: FETCH_ERROR,
      error
    });
    
    export const fetchMovies = () => dispatch => {
      dispatch({type: FETCH_MOVIES})
    
      return fetch(API_URL)
        .then(res => res.json())
        .then(data => dispatch(receivedMovies(data)))
        .catch(err => dispatch(onError(err)));
    }  
    

    🧐 A full working demo is here: https://codesandbox.io/s/7h56r