I am working on my first bigger react project and found this bug/feature which I don know how to solve.
I want to edit the user by modal. I opened it, but when I change something in the field, parents components state changes too. Child component is changing parent state without passed function.
I tried changing variables name because I think it is impossible to change parent components state. Then I started logging when the state is changed. On first call of this.props.log()
in handle change, the state is changed.
Parent component
class UserAdminPage extends Component {
constructor(props) {
super(props);
this.state = {
performingAction: false,
editorIsOpened: false,
selectedUser: {}
}
}
componentDidMount() {
userService.getAll().then(res => {
const users = res;
this.setState({users});
})
}
openEditModal = id => {
const user = this.state.users.filter(u => u.id === id)[0];
this.setState({selectedUser: user}, () => {
this.setState({editorIsOpened: true})
console.log("Selected user", this.state.selectedUser);
});
}
closeModal = () => {
console.log("Close values",this.state.users, this.state.selectedUser);
/*if (!save) {
console.log("Throwing away");
this.setState({editorIsOpened: false});
return;
}
console.log("Saving");
this.setState({editorIsOpened: false});*/
}
log = ()=>{
console.log("Logged user",this.state.users);
}
render() {
const {users} = this.state;
return (
users ? (
<React.Fragment>
<MDBRow className={"my-5"}/>
<UserTable users={users} openEditModal={this.openEditModal}
performingAction={this.state.performingAction}/>
{this.state.editorIsOpened && <UserEditorModal user={this.state.selectedUser}
closeModal={this.closeModal} log={this.log}/>}
</React.Fragment>
) : (
<LaunchScreen/>
)
)
}
}
And child
class UserEditorModal extends Component {
constructor(props) {
super(props);
this.state = {
selectedUser: props.user
}
console.log("Modal user",this.state.selectedUser)
}
handleChange = (e) => {
this.props.log();//here is different output
const [field, value] = [e.target.name, e.target.value]
const parsedValue = parseInt(value)
const user = this.state.selectedUser;
user.hasOwnProperty(field) ? user[field] = isNaN(parsedValue)?value:parsedValue : console.error(`${field} not found on ${user}`);
this.setState({selectedUser:user});
this.props.log();
}
render() {
const user = this.state.selectedUser;
const modal = true;
return (
<React.Fragment>
<MDBModal isOpen={modal} toggle={() => this.props.closeModal(null, false)} fullHeight
position="right">
<MDBModalHeader toggle={() => this.props.closeModal()}>User editor</MDBModalHeader>
<MDBModalBody>
<MDBRow>
<MDBCol size="3">
<MDBInput type='number' name="karma" label="Karma" value={user.karma}
onChange={e => this.handleChange(e)}/>
</MDBCol>
<MDBCol size="3">
<MDBInput type='number' name="money" label="Money" value={user.money}
onChange={e => this.handleChange(e)}/>
</MDBCol>
<MDBCol>
<MDBInput type="text" value={user.email} disabled/>
</MDBCol>
</MDBRow>
<MDBRow>
<MDBCol size="md">
<MDBInput type='text' name="firstName" label="First name" value={user.firstName}
onChange={e => this.handleChange(e)}/>
</MDBCol>
<MDBCol size="md">
<MDBInput type='text' name="lastName" label="Last name" value={user.lastName}
onChange={e => this.handleChange(e)}/>
</MDBCol>
</MDBRow>
</MDBModalBody>
<MDBModalFooter>
<MDBBtn color="secondary"
onClick={() => this.props.closeModal()}>Close</MDBBtn>
<MDBBtn color="primary" onClick={() => this.props.closeModal()}>Save
changes</MDBBtn>
</MDBModalFooter>
</MDBModal>
</React.Fragment>
);
}
}```
I must missing something but couldnt find it. Hope you can find it and help me once again
Guess it's because you are using the reference to change the state. So ultimately when you change the state of the child component, the parent's state will also change but parent won't re-render.
constructor(props) {
super(props);
this.state = {
selectedUser: {...props.user}
}
console.log("Modal user",this.state.selectedUser)
}
Try using this in your child.