AlexisMunoz
AlexisMunoz

Reputation: 134

Passing a JSON as prop in component React JS

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

Answers (1)

Agney
Agney

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:

  1. JavaScript by reference vs. by value
  2. Is JavaScript a pass-by-reference or pass-by-value language?

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

Related Questions