Shallow vs. Deep merge in React.js
Tags: react, state, merge
Published: 2021-11-19 13:55:30

**Prerequisites **for this article – fundamentals of React.js 

----

### <u>Intro</u>

When it's required to keep only single state as primitive of string , number , or any relevant , there is no need to use deep merge approach , conversely – make use of `shallow merge` . However for more complex scenarios , when previous state mostly required to be *preserved* i.e. *not overwritten* – as in case of shallow merge which overwrites , the following i.e. **`deep merge` stays per recommendation and even requirement to use** . Just like in functional component good practice is too copy a state and only then modify it (*indirect mutation a.k.a. deep merge*) rather than passing value instead function as direct mutation (*a.k.a. shallow merge*) , same logic applies when objects used for initial render and , of course , for subsequent re-render Compared with containers (objects, arrays) , primitives are relatively insensitive to direct mutations . Let's examine a couple of examples conceptually without digging too much into React.js code theory itself . First examples shows classic approach : the argument passed as value rather than function callback , next example will consider the opposite . Keep in mind both examples pivots around primitive value being modified for the imaginative Counter component . Consider the initial state for both **Examples 1 & Example 2** utilizing `useState hook` as follows :

### <u>Let's practice</u>

```javascript
const [counter, setCounter] = useState(0);
```

**Example 1**
```javascript
setCounter(counter + 1) // shallow (not recommended)
```

**Example 2**
```javascript
setCounter(prevCounterState => prevCounterState + 1) // deep (recommended) ...
// .., especially when heavy computations might be foreseen)
```

> Note : Although Example 2 takes precedence over Example 1 as recommendation , both examples technically would work 'cause hereby primitive over data structure (container) used

---

Now as we revised the `shallow vs. deep` merge for primitive such as number , let's consider similar logic applied with an object supplied as value with some sub-values (properties) within . Let's consider the following Example 3 :

**Example 3**

```javascript
import {useState} from 'react'

const App = () => {
    const [clicks, setClicks] = useState( {
      left: 0, right: 0,
    } )

    const handleLeftClick = ()=> {
      /* clicks commented out to produce uncommon behaviour */
      setClicks( {/* ...clicks,  */left: clicks.left + 1} )
    }
    
    const handleRightClick = ()=> {
      /* (...object spread operator used to copy last value [deep merge]) */
      setClicks( {...clicks , right: clicks.right + 1} )
    }

    return (
      <div>
        <button onClick={handleLeftClick}>Left click {clicks.left}</button>
        <button onClick={handleRightClick}>Right click {clicks.right}</button>
      </div>
    )
}

export default App;
```

<details>
  <summary>Example 3 in detail:</summary>
  <p>
     Now what happens if e.g. `Left click` pressed a few times first : within a first few click `Left click` gets incremented by 1, 2, 3,.. etc. , from which the very first click of `Left click` changes the `Right click`  value  from `0` to `undefined` (*more precisely number zero just disappears*) . Why this happens , is because if `Left click` pressed it does not copy path properties of left & right , accessing and modifying only left : from that moment no more property of `right : value` exists as was set initial . This demonstrates that sharing properties misbehaviour might be atomic – no way to get back and fix it . Of course this is mocked scenario as I commented `...`**click**'s `spread operator` value out on purpose ! Within this moment technically we can continue incrementing `Left click` by + 1s , but if `Right click` pressed for the first time ever , it gets `NaN` : from JS perspective everything seems to be clear i.e. `undefined + 1` gives you `NaN` ( [just the way it is](https://www.youtube.com/watch?v=GlRQjzltaMQ) ) , but what is happening in terms of perspective of React.js which recommends (more precisely ***requires*** pureness within state change) says about it ? Because technically initially both object spread operators evaluated and assigned for each `handleLeftClick & handleRightClick` , from which the following i.e.`handleRightClick` expects both object values (left and right) even if only one of those will be modified within respective handler , so what happens next when we press `Right click` for state to change, the `Right click` expects property of `right: 0` to be accessible which due to to initial click on the `Left click` did disappear . What does it tell us about overall ? It tries to tell that direct (*a.k.a. shallow merge*) which overwrites the very last state , is not a best practice if more than one (sub-)value within container (in this case – object) used . First direct mutable sate clicks of `Left click` kinda did "eliminated" the `right: 0` sub-value (property) from the object which subsequently resulted into `NaN` as we tried to add 1 (+1) to already non existing property of the object . If this is clear enough , hopefully it is , you may ask what would happened if `Right click` gets clicked for the very first time instead – opposite scenario . Well pretty much interesting thing happens tho . Instead of writing one more paragraph try by oneself [[source](https://codepen.io/projektorius96/pen/ZEJzbOw)] . 
  </p>
</details>

---

The final example tries to touch same logic applied to object but with an array container , this time no `NaN` or similar produced , so no worries , I won't get into greater detail , rather just touch the surface of the following :

**Example 4**

```javascript
import {useState} from 'react'

const App = () => {
    const [left, setLeft] = useState(0)
    const [right, setRight] = useState(0)
    const [allClicks, setAll] = useState([])

    const handleLeftClick = ()=> {
      setAll(allClicks.concat('L'))
      // (...array spread operator used to copy last value [deep merge])) 
      // & is equivalent to setAll([...allClicks, 'L'])
      setLeft(left + 1)
    }
    
    const handleRightClick = ()=> {
      setAll(allClicks.concat('R'))
      // otherwise equivalent to :..
      // setAll([...allClicks, 'R'])
      setRight(right + 1)
    }

    return (
      <div>
        <button onClick={handleLeftClick}>Left click {left}</button>
        <button onClick={handleRightClick}>Right click {right}</button>
        <p>{allClicks.join(' ')}</p>
      </div>
    )
}

export default App;
```

Same logic has been followed : first of all 1) copy the state , only when the state copied , 2) modify it ! In this case it's done with `array-like spread operator` or `.concat()` , keeping in mind `.push()` would do the job for `shallow merge` which mostly not a case we want to do , instead merge it deeply preserving data by modifying it **in**directly ! That's all folks .

---

If any typos found and (or) suggestions could be made, please leave it in the comment section below . Thank you and see you in the next one !

---

<u>Related references></u>

- [spread-vs-rest in practice](https://codesandbox.io/s/spread-vs-rest-operator-inpy2?file=/src/index.js)