Reputation: 41
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
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