React setState changes the value of all objects in different state variable

33 Views Asked by At

Here's my code:

import "./styles.css";
import { useState } from "react";


export default function App() {
  const [input, setInput] = useState("")
  const [list, setList] = useState([
    {key:584535, value:"Do laundry", isChecked: false},
    {key:439735, value:"Take out trash", isChecked: false},
    {key:589774, value:"Do dishes", isChecked: true},
    {key:573735, value:"code", isChecked: false},
    {key:589739, value:"margin", isChecked: false},
    {key:585735, value:"textbox", isChecked: false}]
    )
  const [history, setHistory] = useState([[]])
  function handleCheckClick(e) {    
    let checkkey = e.target.getAttribute("checkboxkey")
    setHistory([...history, list])
    list.map(
      (element) => {
        if(element.key == checkkey){
          element.isChecked = !(element.isChecked)
        }
      }
    )
    setList([...list])
  }
  function handleRemoveClick(e) {
    let buttonKey = e.target.getAttribute("buttonkey") 
    list.forEach(
      (element, index) => {
        if(element.key == buttonKey){
          setHistory([...history, list])
          setList(list.toSpliced(index, 1))
        }
      }
    )
  }
  function handleSubmit() {
    if(input == ""){return}
    let key = Math.floor(100000 + Math.random() * 900000)
      setHistory([...history, list])
      setList([...list, {key: key, value: input, isChecked:false}]) 
      setInput("")
  }
  function handleHistoryClick(){
    if((history[history.length-1]) == undefined){return}
    setList(history.pop())
    setHistory([...history])
  }
  return (
    <div className="App">
      <h1>To Do</h1>
      <input id="input" type="text" value={input} onChange={(e) => setInput(e.target.value)}></input>
      <button onClick={handleSubmit}>Submit</button>
      <button onClick={handleHistoryClick}>Undo</button>
      <div id="container">
        <ol>
          {list.map(({key, value, isChecked}) =>
            <>
              {isChecked === true && <li id="strike" key={key}>{value}</li>}
              {isChecked === false && <li key={key}>{value}</li>}
              <input type="checkbox" checkboxkey={key} onClick={handleCheckClick}></input>
              <button buttonkey={key} onClick={handleRemoveClick}>Remove</button>
              <br />
           </>)
          }
        </ol>
      </div>
    </div>
  );
}

I have a history array which saves copies of all changes made to the main list array. When I'm trying to resign the isClicked value to the main array with element.isChecked = !(element.isChecked) in list.map, it changes the value in all the history arrays! Here is am image of the console when I console.log history every time I toggle the checkbox with the "textbox" value, the last click was to set it to clicked: Console.log of history

1

There are 1 best solutions below

0
David On

Objects in JavaScript are references. So when two arrays reference the same object(s), mutating an object in one array will affect the object in the other array. Because for any given instance there's only one object, it's just being referenced by more than one array/variable/etc.

Given that this is React code, the simple lesson here is don't mutate state in React. Which is what this line of code is doing:

element.isChecked = !(element.isChecked)

(Additionally, this is a misuse of the .map() function where logically a .forEach() or a loop would be more appropriate. .map() is used for projecting a collection into a new collection of the same size, not as a drop-in replacement for simply iterating to perform an operation.)

Instead, project the array into a new array of new objects when updating state. For example:

setHistory([...history, list]);
setList(list.map(
  (element) => {
    if(element.key == checkkey) {
      return { ...element, isChecked = !element.isChecked };
    } else {
      return element;
    }
  }
));

The key difference here is what the callback to .map() returns. If the "key" matches, it returns a new object built from the existing object plus one modified property. Else, it returns the object as-is. So any object which needs to change results in a new object, rather than mutating the existing one.