Redux state doesn't update with action dispatch

12,143

I haven't run your code, so I can't say offhand if just this will fix it, but your username reducer is returning an object with a username property, when it should be returning the action.username string.

const username = (state = '', action) => {
  switch (action.type) {
    case types.UPDATE_USERNAME:
      return action.username
    default:
      return state;
  }
};

Also, have you verified that you don't have a typo in your types declaration? I see that in your reducer you reference types.UPDATE_USERNAME, but in your action creator you set the type using the string UPDATE_USERNAME.

Share:
12,143
DGaffneyDC
Author by

DGaffneyDC

Updated on June 05, 2022

Comments

  • DGaffneyDC
    DGaffneyDC almost 2 years

    I've gone through every piece of documentation and example project I can find for building a react app with redux and react-router, but I just can't seem to figure out how to get my redux state to update when I dispatch actions. As you can see in this screenshot, actions are dispatching properly, but my store/nextState isn't updating.

    enter image description here

    ACTION:

    export function updateUsername(username) {
      return { type: types.UPDATE_USERNAME, username };
    }
    

    REDUCER (EDIT: I've tried both of these variations):

       /* first variation */
       const username = (
          state = '',
          action,
        ) => {
          switch (action.type) {
            case types.UPDATE_USERNAME:
              return Object.assign({}, state, {
                username: action.username,
              });
            default:
              return state;
          }
        };
    
      /* second variation */
      const username = (
        state = '',
        action,
      ) => {
        switch (action.type) {
          case types.UPDATE_USERNAME:
            return action.username;
          default:
            return state;
        }
      };
    

    REDUCER COMBINATION:

    const user = combineReducers({
      isAuthenticated,
      token,
      password,
      username,
    });
    
    export default user;
    

    REDUCERS/INDEX.JS:

    const rootReducer = combineReducers({
      isFetching,
      open,
      search,
      user,
      routing: routerReducer,
    });
    
    export default rootReducer;
    

    STORE CONFIGURATION:

    import React from 'react';
    import { createStore, compose, applyMiddleware } from 'redux';
    import createLogger from 'redux-logger';
    import thunkMiddleware from 'redux-thunk';
    import { routerMiddleware } from 'react-router-redux';
    import rootReducer from '../reducers';
    import * as actions from '../actions';
    
    function configureStore(history, initialState) {
      const loggerMiddleware = createLogger();
    
      const enhancer = window.__REDUX_DEVTOOLS_EXTENSION__ &&
        window.__REDUX_DEVTOOLS_EXTENSION__();
    
      const store = createStore(
        rootReducer,
        initialState,
        compose(
          applyMiddleware(
            thunkMiddleware,
            loggerMiddleware,
            routerMiddleware(history),
          ),
          enhancer,
        ),
      );
    
      if (module.hot) {
        // Enable Webpack hot module replacement for reducers
        module.hot.accept('../reducers', () => {
          const nextReducer = rootReducer;
          store.replaceReducer(nextReducer);
        });
      }
    
      return store;
    }
    
    export default configureStore;
    

    STORE CREATION:

    const initialState = {};
    
    const store = configureStore(browserHistory, initialState);
    const history = syncHistoryWithStore(browserHistory, store);
    const routes = createRoutes(store);
    
    
    render(
      <Provider store={store}>
          <Router history={history} routes={routes} />
      </Provider>,
      document.getElementById('root'),
    );
    

    AND FINALLY, THE COMPONENT:

    import React, { Component, PropTypes } from 'react';
    import { connect } from 'react-redux';
    import { bindActionCreators } from 'redux';
    import TextField from 'material-ui/TextField';
    import validator from 'validator';
    import className from 'classnames';
    import { Link } from 'react-router';
    import * as AuthActions from '../../actions/AuthActions';
    
    class LoginForm extends Component {
      constructor(props) {
        super(props);
        this.handleUsernameChange = this.handleUsernameChange.bind(this);
        this.handlePasswordChange = this.handlePasswordChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      handleUsernameChange(e) {
        this.props.actions.updateUsername(validator.escape(e.target.value.trim()));
      }
    
      handlePasswordChange(e) {
        this.props.actions.updatePassword(validator.escape(e.target.value.trim()));
      }
    
      handleSubmit(e, getState) {
        e.prevent.default();
        const user = { username: this.props.user.username, password: this.props.user.password };
        console.log(user);
        this.props.actions.loginUser(user);
      }
    
      render() {
        return (
              <form autoComplete="off" onSubmit={this.handleSubmit} className='login-form'>
                <TextField
                  type="username"
                  autoFocus="true"
                  floatingLabelText="Username"
                  floatingLabelFixed={true}
                  autoComplete="off"
                  onChange={this.handleUsernameChange}
                  />
                <br/>
                <TextField
                  type="password"
                  autoFocus="true"
                  floatingLabelText="Password"
                  floatingLabelFixed={true}
                  autoComplete="off"
                  onChange={this.handlePasswordChange}
                  />
              </form>
        );
      }
    }
    
    function mapStateToProps(state) {
      return {
        user: state.user,
      };
    }
    
    function mapDispatchToProps(dispatch) {
      return {
        actions: bindActionCreators(AuthActions, dispatch),
      };
    }
    
    
    export default connect(
      mapStateToProps,
      mapDispatchToProps,
    )(LoginForm);
    

    I've been stuck on this for a week now, so any help you can offer would be GREATLY appreciated! Thanks!

  • DGaffneyDC
    DGaffneyDC over 7 years
    Unfortunately, I've tried that format for the reducer, along with a number of other variations as well. None seem to be working. As far as the types declaration, I imported '* as types' in my reducer file. I'll try switching that up now.
  • Paul S
    Paul S over 7 years
    Have you thrown a console.log into your username reducer to ensure that it is being called? (or just added a breakpoint in the devtools)
  • DGaffneyDC
    DGaffneyDC over 7 years
    Yup, it's firing. I'm getting a 200 from my backend authorization service when I dispatch my login action, too, but the same issue with nextState not updating.
  • Paul S
    Paul S over 7 years
    Well, I'm still not seeing anything. Have you tried stepping through the reducer returned by combineReducers with a debugger? github.com/reactjs/redux/blob/v3.6.0/src/… That should verify the return values of all of your reducer functions.
  • DGaffneyDC
    DGaffneyDC over 7 years
    So I found a bit of a work around, but it's throwing all sorts of eslint errors: By changing the "return action.username" to "return state = action.username", my state up updating with all dispatches. The error eslint is throwing though is "Return statement should not contain assignment". But hey, if it works it works...
  • İsmail Atkurt
    İsmail Atkurt about 6 years
    You should keep state mutable. Setting as "state=something" can change whole state of application. Instead, it is expected to return state from a reducer as "return Object.assign({}, state, action);"