简体   繁体   中英

Append an object to an array, why does "push" not work?

I just want to understand why line 33 does not work (marked out), but line 32 does work. Why am I unable to simply push an object to the array? Shouldn't these two lines (line 32 and 33) do the exact same thing?

Reference: https://codesandbox.io/s/lingering-wood-33vtb?file=/src/App.js:0-927

import React, { useState } from "react";

function ThingsToDo({ thing }) {
  return <div>{thing.text}</div>;
}

function ToDoForm({ add }) {
  const [value, setValue] = useState("");

  const updateKey = (e) => {
    e.preventDefault();
    if (!value) return;
    add(value);
    setValue("");
  };

  return (
    <form onSubmit={updateKey}>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
    </form>
  );
}

function App() {
  const [todos, setTodos] = useState([{ text: "Hi" }, { text: "Bye" }]);

  const Addtolist = (text) => {
    const newToDo = [...todos, { text }];
    //     const newToDo = todos.push({text})
    setTodos(newToDo);
  };

  return (
    <div>
      {todos.map((todo, index) => (
        <div>
          <ThingsToDo thing={todo} />
        </div>
      ))}
      <ToDoForm add={Addtolist} />
    </div>
  );
}

export default App;

You can't mutate a state directly in react. In your 2 cases:

    const newToDo = [...todos, { text }];

This one creates a copy of the todos array and adds your extra item. newTodo is a copy and then when you setTodos(newTodo) you set the state to the new copied array.

 const newToDo = todos.push({text})

This one tries to push {text} into todos which is a state. You can't mutate a state directly you have to do this through setTodos().

If you are really set on using push then you can copy the state of todos first then push into the copied array but I think this is extra unecessary work.

You have to do as following,

// const newToDo = [...todos, { text }];
const newToDo = JSON.parse(JSON.stringify(todos));
newToDo.push({ text });
setTodos(newToDo);

Reasons:

  1. You are attempting to mutate the state variable todos (will not work), you have to use setState
  2. If you really want to mutate and set new state, you have to clone using JSON.parse, JSON.strigify, you can't create a real clone otherwise

As official React document https://reactjs.org/docs/react-component.html says,

NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

You are trying to push an object inside React state array which violates the immutability. To update the react state, you will definitely have to use setState (in your case setTodos since you are building functional component).

And another thing is when you push the array and assign it to a const variable, it will assign the length of the array instead of the array which is what you wanted.

As w3school page says in https://www.w3schools.com/jsref/jsref_push.asp

The push() method adds new items to the end of an array, and returns the new length.

My alternative to solving this problem is to use .concat method of Array type because it will not mutate the original array but it will just return a new array with concatted objects instead.

Example.

setTodos(todos.concat(text));

Ref:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat

1st case

That is not going to work. To trigger the state change you must use the setState to update the state. With the todo.push({ text }) it will update the state but never going to trigger the state changes.

2nd case

push returns the item you added to the array. So in setState you are try to override the current state array with { text : "somText" } . So now in your render method you can't map through the state array & you are getting an Error.

3rd case

You need to understand that arrays are reference type. So even you did something like below, it is directly update the todo array since this is not a real copy.

let copy = todo;
copy.push({text});
setTodo(copy)

There are two ways that you can copy.

  1. Shallow Copy
  2. Deep Copy

In Shallow Copy you only copies the values and the prototype will still refer to the original object.

In deep copy it copies everything. Even the references are copied.

In your case shallow copy is enough for perform the state update. You can do the shallow copy by [...todos, {text}]

In JavaScript if you need to do a deep copy, an easy way to do is,

const deepCpy = JSON.parse(JSON.stringiy(todos))

Also you can use deepCopy method in lodash

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM