Redux: Component stops updating after using combineReducers

Created on 14 Jun 2016  路  5Comments  路  Source: reduxjs/redux

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.

What is the current behavior?

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.

Code example:

This works:

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 doesn't work:

This is the version which doesn't work, the only changes I've made were:

  • I'm now wrapping the loginReducer inside combineReducers
  • I changed the mapStateToProps function in order to get those properties from the nested login object inside the state

index.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:

correct

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?

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 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.

All 5 comments

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:

  • In RECEIVE_LOGIN, what is the source of action.payload.resources? What does the action creator look like?
  • Does the UI update for some cases, and not for others?

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

elado picture elado  路  3Comments

jbri7357 picture jbri7357  路  3Comments

olalonde picture olalonde  路  3Comments

mickeyreiss-visor picture mickeyreiss-visor  路  3Comments

cloudfroster picture cloudfroster  路  3Comments