How can mapStateToProps receive the updated state in react-redux implementation

3.4k Views Asked by At

I'm learning react and redux implementation, and encountered a bug I cannot find a fix for. The function mapStateToProps gets the initial (and empty) state. I'm using seamless-Immutable for the state object, and I know that my reducer is in fact being called and updates the state. I have tried every component-props related methods (componentWillReceiveProps , componentWillMount , componentDidMount , etc..), and mapStateToProps never receives the updated state; always initial empty state. it runs before this.props.dispatch can be called within those methods.

I must be missing something...

relevant files:

1. app/index.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './index.css';
import PlaylistList from '../../components/PlaylistList';
import { fetchPlaylists } from '../../store/playlists/actions';
import { getPlaylists } from '../../store/playlists/reducer';
import { connect } from 'react-redux';

class App extends Component {
  componentWillReceiveProps() {
    this.props.dispatch(fetchPlaylists();
  }
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to My Music Store</h2>
        </div>
        <PlaylistList playlists={this.props.playlists}></PlaylistList>
      </div>
    );
  }
}

function mapStateToProps(state) {
  console.dir(state); // prints the initial state (before reducer run)
  return {playlists: getPlaylists(state)}
}

export default connect(mapStateToProps)(App);

2. actions.js:

import * as api from '../../services/api';
import * as types from './actionTypes';
import _keyBy from 'lodash/keyBy';

export function fetchPlaylists() {
  return async (dispatch, getState) => {
    try{
      const playlists = await api.fetchPlaylists();
      const playlistsById = _keyBy(playlists, (playlist) => playlist.id);
      dispatch( {  type: types.PLAYLISTS_FETCHED, playlistsById} );
    } catch (error) {
      console.error(error);
    }
  }
}

3. reducer.js:

import * as types from './actionTypes';
import _mapKeys from 'lodash/mapKeys';
import _mapValues from 'lodash/mapValues';
import Immutable from 'seamless-immutable';

const initialState = Immutable({
    playlistsById: {},
    currentPlaylist: undefined
  }
);

export default function reduce(state = initialState, action = {}){
  switch (action.type) {
    case types.PLAYLISTS_FETCHED:
      return state.merge({
        playlistsById: action.playlistsById
      })
    default: return state

  }
}

export function getPlaylists(state) {
  let playlistsById = state.playlistsById;
  let playlistIds = _mapKeys(state.playlistsById);
  console.dir({playlistsById, playlistIds}); // prints empty objects
}

4. reducers.js:

import playlistsReducer from './playlists/reducer';

export { playlistsReducer };

5. actionTypes.js

export const PLAYLISTS_FETCHED = 'playlists.PLAYLISTS_FETCHED';
export const PLAYLIST_ADDED = 'playlists.PLAYLIST_ADDED';
export const PLAYLIST_REMOVED = 'playlists.PLAYLIST_REMOVED';
export const PLAYLIST_UPDATED = 'playlists.PLAYLIST_UPDATED';
export const FILTER_BY_SEARCH = 'playlists.FILTER_BY_SEARCH';

6. src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import App from './containers/App';
import './index.css';
import * as reducers from './store/reducers';

const store = createStore(combineReducers(reducers), applyMiddleware(thunk));

ReactDOM.render(
  <Provider store={store}>
      <App />
  </Provider>,
  document.getElementById('root')
);
1

There are 1 best solutions below

3
On

The problem with your implementation is that in mapStateToProps you expect the state argument to refer a state of your playlistsReducer, while in fact it refers to an entire state object with all your reducers combined by combineReducers function.

The correct path to the playlistsReducer in the state would be - state.playlistsReducer. As a result, your mapStateToProps has to look like this:

function mapStateToProps(state) {
  console.dir(state.playlistsReducer); // prints the initial state (before reducer run)
  return {playlists: getPlaylists(state.playlistsReducer)}
}

By using this mapStateToProps, getPlaylists will receive a correct object with needed data to pass required props to the component.

Another major issue is that getPlaylists method doesn't return anything.