React - Learn From Scratch - Part Seven

Introduction 

 
Up until now, you should be clear about the following React concepts. You should go back on the previous parts if you feel weak in any of these concepts.
  • What is React and how it is different from jQuery or Plain JavaScript
  • What is a React Element
  • What is JSX
  • What is the Function Component
  • Passing Data from Parent/Host Component to Child Component
  • Passing Data from Child Component to Host Component
  • Event Handling in React
  • What is a Class Component
In the last part, we ended with an issue/requirement (i.e. Updating UI when data is changed.)
 
In this part, we'll learn:
React updates the UI automatically when we change our data, but it doesn’t do this for all data variables. It should not be doing this for all variables, right? It can have watchers (like Angular) to see which variable is being changed and which UI should be updated. The second option is to let React know that we are updating data. React follows the second option. It introduces a special property with the name ‘state’ in the class component only. If we want React to update our UI automatically when the data changes, we need to store such data in ‘state’ property. Is it not easy? Yes, it is, but with some important points. Let’s check these points quickly and then we’ll learn with some examples.
  • React.Component class gives us a ‘state’ property in our class component.
  • We should initialize ‘state’ property in constructor. Ideally it should be an object which may contain other properties.
  • ‘state’ property must never be updated directly e.g. this.state.data = something. Instead we should always use setState() method provided by React.

setState() function

 
setState() takes an object containing ‘changes’ directly or indirectly. We may pass ‘changes’ object directly to setState() function or we may pass a function to it and then that function returns ‘changes’ object.
  • setState(object): We can pass a ‘changes’ object to update the ‘state’ object.
  • setState(updaterFn,callbackFn): updaterFn function is called by React internally to update state. When React calls it, React provides current state & props to it. We return the ‘changes’ object from this function. callbackFn function is called by React when the state is updated.
setState() does shallow merging. We provide ‘changes’ object to it and it merges changes in ‘state’ object. Shallow merging means
  • It adds properties in ‘state’ object if ‘changes’ object contains but ‘state’ doesn’t
  • Matched properties are overwritten in the ‘state’ object
setState() doesn’t update ‘state’ immediately. It also doesn’t update UI immediately. When we call setState(), We are requesting React that this component & its children need to be re-rendered with the updated state.
 
In the following example, we’ve created a simple Counter component. When we click on the button, it increases the counter variable and shows it in the alert. We also want to show counter value on UI. But the following approach is not updating the UI.
  1. <html>  
  2. <head>  
  3.     <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>  
  4.     <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>  
  5.     <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>  
  6. </head>  
  7. <body>  
  8.     <div id="app">  
  9.     </div>  
  10.   
  11.     <script type='text/babel'>  
  12.           
  13.         class Counter extends React.Component{  
  14.             counter = 0;  
  15.             handleCountMe(){  
  16.                 this.counter++;  
  17.                 alert(this.counter);  
  18.             }  
  19.   
  20.             render(){  
  21.                 return (  
  22.                 <div class="mycontainer" >  
  23.                     <div>{this.counter}</div>  
  24.                     <button onClick={()=>(this.handleCountMe()) } >Count Me In </button>  
  25.                 </div>  
  26.             );  
  27.             }  
  28.         }  
  29.   
  30.         ReactDOM.render(<Counter />,document.getElementById('app'));  
  31.     </script>  
  32. </body>  
  33. </html>  
Let’s update the above example and use the state management feature of React.
  1. <html>  
  2. <head>  
  3.     <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>  
  4.     <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>  
  5.     <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>  
  6.       
  7. </head>  
  8. <body>  
  9.     <div id="app">  
  10.     </div>  
  11.   
  12.     <script type='text/babel'>  
  13.           
  14.         class Counter extends React.Component{  
  15.             constructor(props){  
  16.                 super(props);  
  17.                 this.state = {counter:0,dummy:0};  
  18.             }  
  19.             handleCountMe(){  
  20.                 //takes callback function  
  21.                 this.setState((currState)=>{  
  22.                     //Return object with 'changes'.   
  23.                     //This 'changes' object will be merged with 'state' object  
  24.                     return {counter:currState.counter+1};  
  25.                 });  
  26.             }  
  27.   
  28.             render(){  
  29.                 return (  
  30.                 <div class="mycontainer" >  
  31.                     <div>{this.state.counter}</div>  
  32.                     <button onClick={()=>(this.handleCountMe()) } >Count Me In </button>  
  33.                 </div>  
  34.             );  
  35.             }  
  36.         }  
  37.   
  38.         ReactDOM.render(<Counter />,document.getElementById('app'));  
  39.     </script>  
  40. </body>  
  41.    
  42. </html>  
In the above example,
  1. We added a constructor and initialized ‘state’ property with an object. We added a 'counter' property with 0 in it. We’ve added another ‘dummy’ property to it. It has no usage in this example but it is there to show one case while updating the state.
  2. In handleCountMe() function, we have added ‘state’ update logic. It has become a little complex, we know but that is how we are going to set state. We’ve called setState method and provided it a callback function as a parameter. When React will execute this callback function internally, it will pass the current state to it. We need to return an object (with changes only). We are returning an object with a new value of the counter. Note this object doesn’t contain ‘dummy’ property as there is no change in that property. React will merge this object in ‘state’ property.
  3. Then we’ve used this.state.counter in UI.
Let’s check some more cases to update the state for learning purposes. Let’s uncomment each case snippet to test them one by one. Details of each case are given below.
  1. <html>  
  2. <head>  
  3.     <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>  
  4.     <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>  
  5.     <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>  
  6. </head>  
  7. <body>  
  8.     <div id="app">  
  9.     </div>  
  10.   
  11.     <script type='text/babel'>  
  12.           
  13.         class Counter extends React.Component{  
  14.             constructor(props){  
  15.                 super(props);  
  16.                 this.state = {counter:0,dummy:0};  
  17.             }  
  18.             handleCountMe(){  
  19.   
  20.                 //Case 1 - Will not update UI  
  21.                 //this.state.counter++;  
  22.                   
  23.                   
  24.                 //Case 2 - setState with Object  
  25.                 //this.setState({counter: this.state.counter + 1});  
  26.   
  27.   
  28.                 //Case 3 - setState with Object multiple times (batched)  
  29.                 // this.setState({counter: this.state.counter + 1});  
  30.                 // this.setState({counter: this.state.counter + 1});  
  31.                 // this.setState({counter: this.state.counter + 1});  
  32.                 // this.setState({counter: this.state.counter + 1});  
  33.   
  34.   
  35.                 //Case 4 - setState with updaterFn  
  36.                 // this.setState(function(currState, props){  
  37.                 //     return {counter: currState.counter + 1};  
  38.                 // });  
  39.   
  40.   
  41.                 //Case 5 - setState with updaterFn multiple times   
  42.                 // this.setState(function(currState, props){  
  43.                 //     return {counter: currState.counter + 1};  
  44.                 // });  
  45.                 // this.setState(function(currState, props){  
  46.                 //     return {counter: currState.counter + 2};  
  47.                 // });  
  48.   
  49.   
  50.                 //Case 6: setState with updaterFn & callbackFn  
  51.                 // this.setState(function(currState, props){  
  52.                 //     return {counter: currState.counter + 1};  
  53.                 // },function(){  
  54.                 //     alert('in callback fn' + this.state.counter);  
  55.                 // });  
  56.                   
  57.                 // alert('outside ' + this.state.counter);  
  58.   
  59.   
  60.   
  61.                 //Case 7: Updating state directly and using setState to request React for UI update  
  62.                 // this.state.counter++;  
  63.                 // this.setState({});  
  64.   
  65.                 //Case 8: Update state inside Update & return empty object  
  66.                 // this.setState(function(currState, props){  
  67.                 //     currState.counter++;  
  68.                 //     return {};                      
  69.                 // });  
  70.   
  71.             }  
  72.   
  73.             render(){  
  74.                 return (  
  75.                 <div class="mycontainer" >  
  76.                     <div>{this.state.counter}</div>  
  77.                     <button onClick={()=>(this.handleCountMe()) } >Count Me In </button>  
  78.                 </div>  
  79.             );  
  80.             }  
  81.         }  
  82.         ReactDOM.render(<Counter />,document.getElementById('app'));  
  83.     </script>  
  84. </body>  
  85. </html>  
Case 1
 
We are updating the state directly. It will change ‘state’ but UI will not be updated. We must not update ‘state’ directly as it may create unknown issues.
 
Case 2
 
We are using setState(object) method with ‘changes’ object as an argument.
 
Case 3
 
We are using setState(object) method with the ‘changes’ object as argument. We are calling this method multiple times. In this way, we are creating a batch. As React doesn’t update state immediately, we’ll notice that in fourth ‘setState()’, we’ll still have this.state.counter as 0 and we’ll see 1 on UI not 4. This has happened because following objects are passed considering state is not updated immediately.
  1. this.setState({counter: 0 + 1});  
  2. this.setState({counter: 0 + 1});  
  3. this.setState({counter: 0 + 1});  
  4. this.setState({counter: 0 + 1});  
Case 4
 
We are passing a function to setState() method. React calls this function internally but not immediately. When this function is called, React passes current state (at that time) to it. React also passes ‘props’ to it. What we return from this function is merged in ‘state’. We don’t need to return whole ‘state’ object but an object with properties with updated values.
 
Case 5
 
We are performing Case 4 again but multiple times. Both setState() functions will be called but state will not be updated immediately. Now React has two updaterFn functions in its queue. It will call first updaterFn by passing the current ‘state’ object to it. Currently state.counter is 0. We are adding 1 to it and returning the changes. ‘state’ will be updated with state.counter =1. Now React will call second updaterFn from queue by passing current ‘state' object to it. At this moment, state.counter is 1. We are adding 2 to it in second updaterFn. ‘state’ will be updated with state.counter=3. After all updaterFn functions are executed means state is updated, React may now re-render component & its children.
 
Case 6
 
We are passing two functions to setState() method. First is updaterFn as we discussed above. Second function is callback function and called by React when state is updated. So, if we want to do anything once the state is updated, we may do that in this callback function.
 
Note
As setState() doesn’t update the ‘state’ immediately, we should not expect an updated ‘state’ after function call. We’ll get 0 in alert.
 
Case 7
 
In this example, we are calling setState() after updating state directly. We are passing an empty object so it will not make any change to ‘state’ object but will notify React that UI needs to be re-rendered.
 
Note
We must not update the state directly. This may create issues.
 
Case 8
 
In this example, we are passing updaterFn to setState() but inside function we are updating state object directly and then returning object. This will work but we should avoid doing this.
 
Let’s check another example. In this example, we have only one component i.e. Profiles. We want to show these profiles on UI with a Remove button.
  1. <html>  
  2. <head>  
  3.     <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>  
  4.     <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>  
  5.     <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>  
  6.     <style>  
  7.         .mycontainer {  
  8.           border:1px solid red;    
  9.           width: 200px;  
  10.           float:left;  
  11.           margin-left:3px;  
  12.           padding: 5px;  
  13.         }  
  14.     </style>  
  15. </head>  
  16. <body>  
  17.     <div id="app">  
  18.     </div>  
  19.   
  20.     <script type='text/babel'>  
  21.           
  22.         class Profiles extends React.Component{  
  23.                 mydata =[  
  24.                         {id: 1, name:"Bilal Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials"},  
  25.                         {id: 2, name:"Faisal Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials 2"},  
  26.                         {id: 3, name:"Waqas Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials 3"},  
  27.                         {id: 4, name:"Khurram Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials 4"}  
  28.                     ];  
  29.             constructor(props){  
  30.                 super(props);  
  31.                 this.state = {data:this.mydata};  
  32.             }  
  33.             RemoveProfileHandler(id){  
  34.                 alert(id);  
  35.             }  
  36.             GetProfiles(){  
  37.   
  38.                 return this.state.data.map((obj)=>{  
  39.                             return (  
  40.                              <div class="mycontainer" key={obj.id}>  
  41.                                 <h3>{obj.name}</h3>  
  42.                                 <a href={obj.url}>{obj.urlText}</a>;  
  43.                                 <button onClick ={()=> this.RemoveProfileHandler(obj.id)}>Remove </button>  
  44.                             </div>  
  45.                             );  
  46.                     });  
  47.             }//end of GetProfiles  
  48.   
  49.             render(){  
  50.                 return (  
  51.                 <div  >  
  52.                     {this.GetProfiles()}  
  53.                 </div>  
  54.             );  
  55.             }  
  56.         }  
  57.   
  58.         ReactDOM.render(<Profiles />,document.getElementById('app'));  
  59.     </script>  
  60. </body>  
  61.    
  62. </html>  
In above example,
  1. We’ve profiles data in an array.
  2. We initialized ‘state’ property in constructor with our data.
  3. GetProfiles() function is iterating data and returning ‘array’ of ‘div’ elements. Each ‘div’ element contains profile information with a Remove button. When this button is clicked, we are showing ‘id’ in alert for now.
  4. In render() method, we are just using GetProfiles() result and returning it inside a ‘div’.
Now when users click the ‘Remove’ button, a specific profile should be removed from the UI. Remember, we can’t play with the UI now. But we can play with the data (state) so while working with React, we need to play around state to build UI and then think about state as our primary truth. React will update UI by itself for us. Can we remove the Profile box from UI when Remove button is clicked? We just need to remove a specific object from profile, that’s it. Here is the code snippet for RemoveProfileHandler. In following example, we’ve used the ‘filter’ method to create a new array without that specific profile.
  1. RemoveProfileHandler(id){  
  2.                 //Find and remove element by using id  
  3.                 //There can be multiple ways to do this  
  4.                 this.setState((currState)=>{  
  5.                     //lets use filter and find result without id  
  6.                     var updatedData = currState.data.filter((obj)=> obj.id != id );  
  7.                     return {data:updatedData};  
  8.                 });  
  9. }  
In the following example, We’ve added a new button on page to add a profile. We’ve also added a new function “AddProfileHandler()” to call when ‘Add’ button is clicked. We’ve created a new object for profile. Then we’ve used setState() method to update state. We’ve used array concat function to create a new array with this new object. Then we’ve returned the object.
 
Here is the full code with Remove & Add buttons. Also, check the ‘Remove All’ button.
  1. <script type='text/babel'>  
  2.           
  3.         class Profiles extends React.Component{  
  4.                 mydata =[  
  5.                         {id: 1, name:"Bilal Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials"},  
  6.                         {id: 2, name:"Faisal Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials 2"},  
  7.                         {id: 3, name:"Waqas Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials 3"},  
  8.                         {id: 4, name:"Khurram Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials 4"}  
  9.                     ];  
  10.             constructor(props){  
  11.                 super(props);  
  12.                 this.state = {data:this.mydata};  
  13.             }  
  14.             RemoveProfileHandler(id){  
  15.                 //Find and remove element by using id  
  16.                 //There can be multiple ways to do this  
  17.                 this.setState((currState)=>{  
  18.                     //lets use filter and find result without id  
  19.                     var updatedData = currState.data.filter((obj)=> obj.id != id );  
  20.                     return {data:updatedData};  
  21.                 });  
  22.             }  
  23.             AddProfileHandler(){  
  24.                 //let's create a dummy profile  
  25.                 var newProfile = {id: 5, name:"Bisaam",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials 5"}  
  26.                 this.setState((currState)=>{  
  27.                     //lets use concat to create new array with new object  
  28.                     var updatedData = currState.data.concat([newProfile]);  
  29.                     return {data:updatedData};  
  30.                 });  
  31.             }  
  32.             GetProfiles(){  
  33.   
  34.                 return this.state.data.map((obj)=>{  
  35.                             return (  
  36.                              <div class="mycontainer" key={obj.id}>  
  37.                                 <h3>{obj.name}</h3>  
  38.                                 <a href={obj.url}>{obj.urlText}</a>;  
  39.                                 <button onClick ={()=> this.RemoveProfileHandler(obj.id)}>Remove </button>  
  40.                             </div>  
  41.                             );  
  42.                     });  
  43.             }//end of GetProfiles  
  44.   
  45.             render(){  
  46.                 return (  
  47.                   
  48.                 <div  >  
  49.                     <div>  
  50.                         <button onClick={()=> this.AddProfileHandler()} >Add dummy profile</button>  
  51.                         <button onClick={()=> this.setState((cs)=>   
  52.                         {  
  53.                             return {data:[]};  
  54.                         })}  
  55.                         >Remove all </button>  
  56.                     </div>  
  57.                     {this.GetProfiles()}  
  58.                 </div>  
  59.             );  
  60.             }  
  61.         }  
  62.   
  63.         ReactDOM.render(<Profiles />,document.getElementById('app'));  
  64.     </script>  
Let’s check the final file we have from last part and update it to add following functionalities in it:
  1. Show profile counter updated value on UI
  2. Provide a Remove button to remove a profile from the UI
  3. Provide an Add button to add a new profile to the UI
Can you try on your own to update that file? By the way, here is updated file in case if you are facing any confusion.
  1. <html>  
  2. <head>  
  3.     <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>  
  4.     <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>  
  5.     <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>  
  6.   
  7.     <style>  
  8.         .mycontainer {  
  9.           border:1px solid red;    
  10.           width: 200px;  
  11.           float:left;  
  12.           margin-left:3px;  
  13.           padding: 5px;  
  14.         }  
  15.     </style>  
  16. </head>  
  17. <body>  
  18.     <div id="app">  
  19.     </div>  
  20.   
  21.     <script type='text/babel'>  
  22.         class Profile extends React.Component{  
  23.             counter = 0;  
  24.             constructor(props){  
  25.                 super(props);  
  26.                 this.state = {counter:0};  
  27.                 this.handleCountMe = this.handleCountMe.bind(this);  
  28.             }  
  29.             handleCountMe(id){  
  30.                   
  31.                 this.setState((currState)=>{  
  32.                     //Return state object with 'changes'  
  33.                     return {counter:currState.counter+1};  
  34.                 },function(){  
  35.                     //This function is executed once state is updated  
  36.                     if(this.state.counter >= 3){  
  37.                         if(this.props.onLimitCross){  
  38.                             this.props.onLimitCross(id, this.state.counter);  
  39.                         }  
  40.                     }  
  41.                 });  
  42.   
  43.                 //Accessing state instantly after setState may show old data as setState behaves async  
  44.             }  
  45.   
  46.             render(){  
  47.                 return (  
  48.                 <div class="mycontainer" >  
  49.                     <span>{this.state.counter}</span>  
  50.                     <h3>{this.props.name}</h3>  
  51.                     <a href={this.props.url}>{this.props.urlText}</a>;  
  52.                     <button onClick={()=>(this.handleCountMe(this.props.eid)) } >Count Me In </button>  
  53.                     <button onClick={()=>(this.props.onRemoveProfile(this.props.eid))} >Remove</button>  
  54.                 </div>  
  55.             );  
  56.             }  
  57.         }  
  58.           
  59.         class Profiles extends React.Component{  
  60.             LimitCrossHandler(id, counter){  
  61.                 alert('Current value of Counter for id:' + id + ' is: ' + counter);  
  62.             }  
  63.   
  64.             render(){  
  65.                 var profilesElem = this.props.data.map(  
  66.                     (obj)=>(  
  67.                         <Profile   
  68.                         key={obj.id}   
  69.                         eid={obj.id}   
  70.                         name={obj.name}   
  71.                         url={obj.url}   
  72.                         urlText={obj.urlText}   
  73.                         onLimitCross={this.LimitCrossHandler}  
  74.                         onRemoveProfile={this.props.onRemoveHandler}  
  75.                         />)  
  76.                 );  
  77.                 return profilesElem;  
  78.             }  
  79.         }  
  80.          
  81.         //Created a top level component  
  82.         class MyApp extends React.Component{  
  83.             mydata = [  
  84.                 {id: 1, name:"Bilal Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials"},  
  85.                 {id: 2, name:"Faisal Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials 2"},  
  86.                 {id: 3, name:"Waqas Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials 3"},  
  87.                 {id: 4, name:"Khurram Shahzad",url:"https://www.youtube.com/c/LearnInUrdu139",urlText:"Learn in Urdu Tutorials 4"}  
  88.                 ];  
  89.             constructor(props){  
  90.                 super(props);  
  91.                 this.state = {data:this.mydata};  
  92.                 this.RemoveProfileHandler = this.RemoveProfileHandler.bind(this);  
  93.             }  
  94.             RemoveProfileHandler(id){  
  95.                 //Find and remove element by using id  
  96.                 //There can be multiple ways to do this  
  97.                 this.setState((currState)=>{  
  98.                     //lets use filter and find result without id  
  99.                     var updatedData = currState.data.filter((obj)=> obj.id != id );  
  100.                     return {data:updatedData};  
  101.                 });  
  102.             }  
  103.             render(){  
  104.                 return (  
  105.                   
  106.                 <div class="maincontainer">  
  107.                     <div>  
  108.                           
  109.                     </div>  
  110.                     <Profiles data={this.state.data} onRemoveHandler={this.RemoveProfileHandler} />  
  111.                 </div>  
  112.             );  
  113.             }  
  114.         }  
  115.           
  116.   
  117.         ReactDOM.render(<MyApp />,document.getElementById('app'));  
  118.     </script>  
  119. </body>  
  120.    
  121. </html>  

Practice Tasks

  1. You may add an "Add Profile" button in the above work.
  2. You may add a “Remove all” in the above work.
  3. Add a new button “Select” in profile. When the user clicks on it, it highlights the  profile background to show it as selected. Also, it shows the selected profile count somewhere on the page.

Extra/Bonus Learning on Shallow Merging

 
We’ve seen above that React uses Shallow Merging when we update state using setState(). Shallow Merging is merging one or more source objects in target object. Any new property in the source object is added in target object. Matched properties are overridden in target. It happens at one level, and no deep copying.
 
Shallow Merging using Object.assign() function
 
We can merge multiple objects in one object using the Object.assign() method.
  1. <html>  
  2. <head>  
  3. </head>  
  4. <body>  
  5.     <script >  
  6.         //Shallow Merging using Object.assign  
  7.           
  8.         //Object.assign(target,source)  
  9.   
  10.         var state = {id:1,name:"Bilal", age:100,address:{city:'lahore'}};  
  11.         var changeObj = {age:200,nic:'1234', address:{postcode:'122'}};  
  12.                   
  13.         /* 
  14.         Following will copy properties from source to target 
  15.         It adds if a property exists in source but not in target 
  16.         It overwrite if a property exists in target 
  17.         It returns target object after changes. 
  18.         Here 'state' is target & 'changeObj' is source 
  19.         */               
  20.   
  21.         state = Object.assign(state,changeObj);  
  22.   
  23.         //Can you check what happened to 'address' property in target?  
  24.         //Answer: It found 'address' property in target and 'overwrote' it  
  25.   
  26.         console.log(state);  
  27.     </script>  
  28. </body>  
  29. </html>  
In above example, we merged ‘changeObj’ in ‘state’.
  • ‘age’ property exists in ‘state’ so it is overridden with new value i.e. 200.
  • ‘nic’ property is new in ‘changeObj’ so it is added in ‘state’ object
  • ‘address’ property exists in ‘state’ so it is overridden with new value i.e. {postcode:’122’}
Are we expecting something else? If we are expecting that ‘postcode’ will be added to ‘address’ object in target, that will be deep merging. Object.assign() does ‘shallow merging’.
 
But what if we want to merge changes to ‘address’ object too? For such changes, we’ll have to consider ‘address’ as a separate object. Here is how we can do this:
  1. <script >  
  2.         //Shallow Merging using Object.assign  
  3.         //Object.assign(target,source)  
  4.   
  5.         var state = {id:1,name:"Bilal", age:100,address:{city:'lahore'}};  
  6.         var changeObj = {age:200,nic:'1234'};  
  7.         var onlyAddObj = {postcode:'122'};  
  8.           
  9.         state.address = Object.assign(state.address, onlyAddObj);  
  10.   
  11.         state = Object.assign(state,changeObj);  
  12.           
  13.         console.log(state);  
  14.     </script>  
In the above example, we’ve created a separate object for address changes and merged it in actual address object separately. Then we updated the main ‘state’ object with relevant changes separately.
 
Shallow Merging using ES6 spread operator
 
Here is a code sample with comments
  1. <script >  
  2.         //Shallow Merging using spread operator  
  3.   
  4.         var state = {id:1,name:"Bilal", age:100,address:{city:'lahore'}};  
  5.         var changeObj = {age:200,nic:'1234', address:{postcode:'122'}};  
  6.           
  7.         //Following will combine both objects and return new object  
  8.         //It adds if a property exists in second but not ine one  
  9.         //It overwrite if a property exists in first  
  10.         state= {...state,...changeObj};   
  11.   
  12.         //state.address = {...state.address,...changeObj.address};  
  13.         console.log(state);  
  14. </script>  

Summary

 
In this article, we learned a very important feature of React (i.e. State Management). We learned to adjust the setState() function to set state or to tell React to re-render the current component & its children. We must not update the state directly. We also learned shallow merging with some examples.
 
References
 
https://reactjs.org/docs/react-component.html#setstate
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
React without ES6: https://reactjs.org/docs/react-without-es6.html


Similar Articles