3

So my code looks like this, where I have two same states state1 and state2. Clicking on the "change" button will set the state of state1, but in fact, the value of both states are changed, showing by clicking on the "Show" button. What is causing this issue?

import React, { useEffect, useState } from "react";

interface Obj {
  num: number
}

const App = () => {

  const [state1, setState1] = useState<Array<Obj> | null>(null)
  const [state2, setState2] = useState<Array<Obj> | null>(null)

  useEffect(() => {
    const a = [{num: 1} as Obj]
    setState1(a)
    setState2(a)
  }, [])

  return (
    <div>
      <button
        onClick={() => {
          if (state1 == null) return null
          const temp = [...state1]
          temp[0].num = 100;
          setState1(temp)
        }}  
      >
        Change
      </button>
      <button 
        onClick={() => {
          console.log(state1, '11');
          console.log(state2, '22');
        }}
      >
        Show
      </button>
    </div>
  )
}

export default App
Ji Frank
  • 71
  • 1
  • 6

1 Answers1

3

This is because JavaScript objects are passed by reference. Since your object:

const a = [{num: 1} as Obj];

is passed to both state setters, both will reference the same object in memory essentially. So when you later update one of them by setting obj.num = 100, the change will affect both state arrays since they contain the same object. Using the array spread syntax in your temp variable only creates a shallow copy, the object inside is still the same. To ensure this doesn't happen, you can deep clone the object before passing it to the second state setter. For example:

useEffect(() => {
  const a = [{num: 1} as Obj]
  setState1(a)
  // used map() and destructuring syntax here just to clone
  setState2(a.map((obj) => {
    const clone = {...obj, num: 100 }
    return clone
  })))
}, [])

Note that object spread syntax also creates only a shallow copy. If the objects in the array contain references to other objects you will have to clone those too using the same approach.

Jared Smith
  • 19,721
  • 5
  • 45
  • 83
Irfanullah Jan
  • 3,336
  • 4
  • 24
  • 34
  • 1
    It is not pass **by** reference, It's always pass by value, but for objects, the value of the variable is a reference. See [Is JavaScript a pass-by-reference or pass-by-value language?](https://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language) – Enfield Li Aug 16 '22 at 10:53
  • Ah thanks for pointing that out, I didn't know. Essentially what this means is that if we change the whole object in one state1, it will not affect state2 but if we change the internals, it after both. I kinda knew this from experience but now have a better understanding. – Irfanullah Jan Aug 16 '22 at 12:53
  • Thank you, Jared and Irfanullah! That works fine now! – Ji Frank Aug 16 '22 at 19:45