Hello everyone, I'm facing a difficult situation which I cannot fix, every suggestion on this repo or stackoverflow won't work, so I think it may be a bug.
I have a Header component which should update after login data gets fetched. It checks a user's name and displays a welcome message.
When creating a store without using combineReducers, using only the loginReducer I've created everything works fine, state gets updated and then the component gets updated too. However, when using combineReducers, even though I use the same single reducer inside it, the component stops updating. I'm also using a logger middleware to display state changes and the state seems to be getting updated properly.
Please notice that I'm only showing relevant file parts here.
index.js
const loggerMiddleware = createLogger();
// Here I'm creating the store with a single reducer function
const store = createStore(loginReducer, applyMiddleware(thunkMiddleware, loggerMiddleware));
const router = createRouter();
ReactDOM.render(<Provider store={store}>
{ router }
</Provider>,
document.getElementById('root')
);
loginReducer.js
// Please notice that here I'm not mutating state in any way
// I'm always returning a new state as recommended by the docs
const ACTION_HANDLERS = {
[REQUEST_LOGIN]: (state, action) => {
return ({ ...state, username: action.payload.username, authHeader: 'Basic ' + base64Encode(action.payload.username + ':' + md5(action.payload.password))});
},
[RECEIVE_LOGIN]: (state, action) => {
return ({ ...state, resources: action.payload.resources, isCashier: action.payload.isCashier });
},
[LOGOUT]: (state) => {
return ({ ...state, username: null, resources: {}, authHeader: null, isCashier: null });
}
}
const initialState = { isCashier: null, resources: {}, username: null };
export default function loginReducer (state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}
export default loginReducer;
LoginContainer.js
const mapActionCreators = {
doLogin,
doLogout
}
const mapStateToProps = (state) => ({
resources: state.resources,
username: state.username
})
export default connect(mapStateToProps, mapActionCreators)(Login)
This is the version which doesn't work, the only changes I've made were:
loginReducer inside combineReducerslogin object inside the stateindex.js
const rootReducer = combineReducers({
login: loginReducer
});
const loggerMiddleware = createLogger();
// Now I'm not using the raw `loginReducer` anymore
const store = createStore(rootReducer, applyMiddleware(thunkMiddleware, loggerMiddleware));
const router = createRouter();
ReactDOM.render(<Provider store={store}>
{ router }
</Provider>,
document.getElementById('root')
);
loginReducer.js -> This stays the same as above
LoginContainer.js
const mapActionCreators = {
doLogin,
doLogout
}
// Here I'm getting properties from `state.login` instead of `state` now due to the use of combineReducers
const mapStateToProps = (state) => ({
resources: state.login.resources,
username: state.login.username
})
export default connect(mapStateToProps, mapActionCreators)(Login)
Here's an image in which I describe the states after dispatching each one of the actions:

Whenever an action is dispatched and the previous and next state are logged, they are correct, but my component won't get updated.
Am I missing something here or is this really a bug?
Honestly, the odds of this being a bug in combineReducers is vanishingly small. That said, your code looks reasonable at first glance.
Any chance you can post a link to a sample project that reproduces this problem?
@markerikson I thought the same thing. I'll try to create a dummy example in order to demonstrate it.
Couple other questions:
RECEIVE_LOGIN, what is the source of action.payload.resources? What does the action creator look like?Also, there's a number of dev tools listed over at https://github.com/markerikson/redux-ecosystem-links/blob/master/devtools.md, some of which can do things like checking your state for accidental mutations. Might want to try adding some of those to the project to see if they note anything.
@markerikson The source of action.payload.resources comes from the action creator, which retrieves it through AJAX.
The action creator looks like this:
export const doLogin = (username, password) => {
return function (dispatch, getState) {
dispatch(requestLogin(username, password));
return request
.post('http://<myLocalhostUrlHere>')
.send({ username, password })
.set('Accept', 'application/json')
.end(function(err, res) {
if (res !== undefined) {
const responseObject = JSON.parse(res.text);
dispatch(receiveLogin(responseObject.response.data.resources, responseObject.response.data.cashier));
}
});
}
}
Whenever I use combineReducers the UI won't update after logging in. react-router still works, but my Header component won't update.
Thanks for your time and for that devtools link, I'll definitely try it.
Just solved this.
Turns out I was having problems with the props passed to the header, I was mapping a part of the state which won't change to the props of the header component, so it would not re-render (if anyone has a doubt about this please check shouldComponentUpdate on the docs).
Thanks for the link to the dev tools extensions, it helped me a lot.
Most helpful comment
Just solved this.
Turns out I was having problems with the props passed to the header, I was mapping a part of the state which won't change to the
propsof the header component, so it would not re-render (if anyone has a doubt about this please checkshouldComponentUpdateon the docs).Thanks for the link to the dev tools extensions, it helped me a lot.