Reputation: 31
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')
);
Upvotes: 0
Views: 3485
Reputation: 1582
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.
Upvotes: 2