Reputation: 134
I have a component with another child component and I am passing a JSON state variable of the parent component as prop to the child component, but when I modify the JSON in the child component the state variable of the parent is been modified too. It doesn't have sense beacuse it just happens with JSON props, but if i use strings, numbers or arrays it works good and child state variables are just modified. These are my components:
class Child extends React.Component{
constructor(props){
super(props)
this.state={
test2: this.props.data,
}
this.changeTextField = this.changeTextField.bind(this)
}
changeTextField(e){
let data = this.state.test2
data['name'] = e.target.value
this.setState({test2: data})
}
render(){
return(
<div>
<input type="text" value={this.state.test2['name']} onChange={this.changeTextField}/>
</div>
)
}
}
class Parent extends React.Component{
constructor(props){
super(props)
this.state={
test: {name: "hola"},
editing: false,
}
this.edit = this.edit.bind(this)
this.cancel = this.cancel.bind(this)
}
edit(){
this.setState({editing: true})
}
cancel(){
this.setState({editing: false})
}
render(){
return(
<div>
{(this.state.editing) ?
<React.Fragment>
<Child data={this.state.test}/>
<button onClick={this.cancel}>cancelar</button>
</React.Fragment>
:
<React.Fragment>
<h1>{this.state.test['name']}</h1>
<button onClick={this.edit}>edit</button>
</React.Fragment>
}
</div>
)
}
}
$(document).ready(function(){
ReactDOM.render(<Parent/>, document.getElementById("app"))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script src="parent.jsx" type="text/babel"></script>
</body>
</html>
Upvotes: 0
Views: 264
Reputation: 19224
This is how JavaScript works with Objects. They are always passed by reference and the others (strings, booleans, numbers as you mentioned) are primitives meaning they are immutable.
There are many amazing answers on SO already regarding these:
How do we get around this?
In your snippet, where you say data['name'] = e.target.value
you are still mutating the state object, which is surely a Not To Do in React. You can read upon Power of not mutating content in React Docs.
You could create a copy of the test2
and choose to mutate that instead:
const data = {...this.state.test2};
data['name'] = e.target.value
But there is a chance that this function gets called programatically, this will run into an error because setState
is async. Instead it gives us a functional version to deal with:
this.setState(prevState => ({
test2: {
...prevState.test2,
name: value,
}
}));
Full Demo:
class Child extends React.Component{
constructor(props){
super(props)
this.state={
test2: this.props.data,
}
this.changeTextField = this.changeTextField.bind(this)
}
changeTextField(e){
const value = e.target.value
this.setState(prevState => ({
test2: {
...prevState.test2,
name: value,
}
}))
}
render(){
return(
<div>
<input type="text" value={this.state.test2['name']} onChange={this.changeTextField}/>
</div>
)
}
}
class Parent extends React.Component{
constructor(props){
super(props)
this.state={
test: {name: "hola"},
editing: false,
}
this.edit = this.edit.bind(this)
this.cancel = this.cancel.bind(this)
}
edit(){
this.setState({editing: true})
}
cancel(){
this.setState({editing: false})
}
render(){
return(
<div>
{(this.state.editing) ?
<React.Fragment>
<Child data={this.state.test}/>
<button onClick={this.cancel}>cancelar</button>
</React.Fragment>
:
<React.Fragment>
<h1>{this.state.test['name']}</h1>
<button onClick={this.edit}>edit</button>
</React.Fragment>
}
</div>
)
}
}
$(document).ready(function(){
ReactDOM.render(<Parent/>, document.getElementById("app"))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script src="parent.jsx" type="text/babel"></script>
</body>
</html>
Upvotes: 2