4

So I have two input fields:

  <TextField onChange={this.handleUserUsername.bind(this)}
    value={this.props.userInfo.username}
  />

  <TextField onChange={this.handleUserEmail.bind(this)}
    value={this.props.userInfo.email}
  />

once entered, I would like to store them in the state of an object named 'userInfo', like so:

  handleUserUsername(event){
    this.props.actions.updateUsername(event.target.value)
  }

  handleUserEmail(event){
    this.props.actions.updateEmail(event.target.value)
  } 

and the action creators are:

  updateUsername: function(eventValue){
    return {
      type: 'UPDATE_USERNAME',
      username: eventValue
    }
  },

  updateEmail: function(eventValue){
    return {
      type: 'UPDATE_USERNAME',
      email: eventValue
    }
  }

and the reducer is:

function(userInfo={}, action){
  switch(action.type){
    case 'UPDATE_USERNAME':
      return {
        username: action.username
      }

    case 'UPDATE_EMAIL':
      return {
        email: action.email
      }

But once texts are inputted into username TextField, it displays whatever it is typed in the TextField and correctly stores 'Z' for the 'username' and logs the following (in the example, 'Z' was typed in):

enter image description here

Then when a text, 'M' in this example, is entered into the email TextField, the username state becomes 'undefined' when it should stay as Z, and email state is not even stored:

enter image description here

In the log for the example, what I am hoping to see is:

userInfo: Object
    username: "Z"
    email: "M"

And I am using the following to access state and actions:

function mapStateToProps(state) {
  return state
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(actions, dispatch)
  }
}

Why isn't it correctly storing the state for the 'userInfo' object? What would be the proper way to update such an object? Eventually I want to store the 'userInfo' object into an array called users, where it would store all the user information.

EDIT **

When I entered 'q' in the username textfield, the following shows:

enter image description here

Then when I enter 'm' in the password textfield, both become undefined now:

enter image description here

EDIT 2 **

const registerReducer = function(userInfo={}, action){
  switch(action.type){
    case 'UPDATE_USERNAME':
      return {
        ...userInfo,
        username: action.username
      }

    case 'UPDATE_EMAIL':
      return {
        ...userInfo,
        email: action.email
      }

2 Answers 2

2

In your example you missed that you should be returning the existing state along with the changes that happened as result of your action, altering the previous state.

This is what a reducer does:

Actions describe the fact that something happened, but don’t specify how the application’s state changes in response. This is the job of a reducer.

What you need to do is to always return a new state object that is the result of applying your reducer to the old state object for the particular action that happened.

Instead of

case 'SOME_ACTION_TYPE':
  return {
    someProperty: 'theNewValue'
  }

What you should do is:

case 'SOME_ACTION_TYPE':
  return {
    ...state, // notice how I create a new object, but preserve all of the old
    propertyThatIChangedInMyReducer: 'theNewValue' // this will override the old
  }

Basically, you take the reducer, apply the changes that you would for the specific action that happened and return that new object, that also contains the other part of the state tree that remained unchanged. You missed returning the other part.

If let's say you had a password field too that you would treat in a similar manner, you would do something like this:

case 'UPDATE_PASSWORD':
  return {
    ...state, //preserve current state, apply changes to it below
    password: action.password,
  }; 
Sign up to request clarification or add additional context in comments.

7 Comments

Thank you so much for the clarification! I took your advice and tried implementing it, provided in the original post EDIT 2 **, but getting the following error: Unexpected token (5:8) at where '...' starts, and can't to figure out what's wrong.
It means you are not using ES6 then. I strongly recommend you switch to using it because most of the examples and sources that you will find that are on React and Redux will be using it. If unable to or you would rather not, you can use Object.assign to preserve the old attributes which remain unchanged on your state.
I would prefer ES6 if that's the case. Is there a way to tell which version I am using? Seems like the '...' works in some cases but don't in others, so want to confirm. Also how do I go about switching to ES6?
@ElodSzopos What you need to do is to always return a new state object that is the result of applying your reducer to the old state object for the particular action that happened Why should you return a new state object every time in a reducer? What is/are the pro/pros? Why don't just update the current state? Is it because then you can access the application's state history with all the changes made so far?
@tonix The idea behind redux is that you have uni-directional data flow and your reducers are pure functions (no side-effects). Mutating state or props can and will cause your app to misbehave and/or error out. The base principle of redux cannot be broken, the libraries rely on it. You get the benefit of being able to record actions, replay them, time-travel, have a reliable source of data which makes errors predictable and easy to understand. Hope that helps.
|
0

I think the problem is that you create updated state object that contains information only about changed fields. Due to one of the Redux's principles its state should be immutable and each time you want to update it you should create new state object with needed changes.

In your case, you should return both fields when creating new state object (even if one of them didn't change):

function(userInfo={}, action){
  switch(action.type){
  case 'UPDATE_USERNAME':
    return {
      username: action.username,
      email: userInfo.email     // userInfo is current state, we should save old value
    }

  case 'UPDATE_EMAIL':
    return {
      username: userInfo.username,  // userInfo is current state, we should save old value
      email: action.email      
    }

By this you create new state which contains fields that didn't changed(with same values) and fields that changed(with new values)

EDIT :

more examples:

assume that state of your application is the following:

oldState = {
    email: "oldValue",
    userName: "oldValue"
}

Then if you trying to change email, you reducer should return state object in the following way:

return Object.assign({}, oldState, {
    email: "newValue"
})

Code above will produce new state object basing on the old state and change email attribute in the new one. Object.assign

As a result your new state will look like this:

{
    email: "newValue",
    userName: "oldValue"    
}

In your example your reducer return state object like this:

{
    email: "newValue",
}

So the redux cannot understand what current value for userName is.

4 Comments

I updated the original post under EDIT ** with new logs. It shows the object state email now, but they both become undefined once the second textfield receives any texts.
But my answer is still actual for you, as also @ElodSzopos mentioned below, in reducer you should return the whole state object (including email, username, password) each time you make changes by actions
I tried implementing as such like I provided in EDIT 2 ** but getting the following error: Unexpected token (5:8) at where '...' starts, and can't to figure out what's wrong.
@JoKo Try my example it should not require ES6 standard. ... is Spread operator which was added in ECMAScript 6, which javascript is implement. All official redux tutorials written on this version, you better to figure out what version do you use.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.