DoReMi
DoReMi

Reputation: 41

React state updating without reason

My problem is simple to understand.

I have an userlist in my state. Filled by a DataBase call. And I also have a selecteduser filled when a element in the userlist is clicked.

When this element from userlist is clicked, a modal is opening with input with a default value filled by selecteduser.

I assigned onChange function to those input, then each change is saved in selecteduser.

But the problem is, each change is also saved in userlist and I really don't understand why.

This userlist doesn't have any setState in the code except for the data call from the database.

class Users extends Component {
 constructor(props) {
  super(props);
   this.state = {
    userlist: [],
    selecteduser: [],
    IsModalUserVisible: false
   };
 }

Then call and store data in userlist.

 componentWillMount() {
 db.collection("user")
 .get()
 .then(collection => {
   this.setState({
     userlist: collection.docs.map(doc => doc.data())
   });
 });
}

Here is the onChange from the input

handleChangeEmail(event) {
 event.preventDefault();
 const selectedUserUpdate = this.state.selecteduser;
 selectedUserUpdate.email = event.target.value;
 this.setState({
  selecteduser: selectedUserUpdate
 });
}

And here the function called onClick on a userlist element.

UserSelected(user) {
 this.setState({
    selecteduser: user,
    IsModalUserVisible: true
  });
}

The userlist.map to show the user list.

{this.state.userlist.map(user => {
  return (
    <UserItem
      callback={() => this.UserSelected(user)}
      key={user.email}
      email={user.email}
    />
  );
})}

And for finish, the user manage modal, opened on click to an user list element.

<ManageUserItem
   isOpen={this.state.IsModalUserVisible}
   email={this.state.selecteduser.email}
   changeEmail={this.handleChangeEmail.bind(this)}
/>

So, when I change the email inside the input, I can see on background that the list also change. And I checked with a console.log(this.state.userlist) in the handleChangeEmail, I can see the userlist is updated too.

I wanted to be clear, but i hope it's not to long to read.

Thanks in advance :)

Upvotes: 1

Views: 69

Answers (1)

lrcrb
lrcrb

Reputation: 910

userlist: collection.docs.map(doc => doc.data()) creates a Javascript Array that at each index holds a memory reference point to the data returned by doc.data(). When you initialize the <UserItem> component, you are providing this component with the same memory reference in the userlist state Array. When a user is clicked, that same reference is passed through the callback prop method UserSelected and in that method the same memory reference is assigned to the state variable selecteduser. Later on when the email change is handled for that selected user, the method handleChangeEmail is operating on a memory reference stored in two places, the userlist Array and the selecteduser Object. When you update an attribute of an Object reference, any other place that Object is referenced will show such a mutation because they are pointing to the same underlying data. One minor alteration that I would suggest for this code is in the constructor method, initialize selecteduser to be an Object ({}) not an Array ([]) since selecteduser is eventually assigned an Object anyway and not an array. Finally, if you'd prefer that selecteduser not be the same Object reference as what is referenced in the userlist Array, you may create a new Object (and thus a new Object reference) from each Object referenced in the userlist Array. Like so:

{this.state.userlist.map(user => {
   // this is one way to do it, you can find others elsewhere on SO for more complex cases.
   // Look into, Javascript as a pass by reference language versus pass by value languages
   // and their nuances. 
   const newObject = JSON.parse(JSON.stringify(user))
   return (
    <UserItem
      callback={() => this.UserSelected(newObject)}
      key={newObject.email}
      email={newObject.email}
    />
  );
})}

Upvotes: 1

Related Questions