React input 在第一次点击时保存输入,然后在第二次点击时将其显示在 UI 中......为什么?!!!以及如何修复它

React input saves the input at first click then shows it in the UI on second click... WHY ?!!! and how to fix it

我正在制作一个带有 React 的待办事项列表应用程序,当您输入一个输入然后单击按钮创建一个新的 todoItem 对象时我遇到了这种错误,它显示一个空对象然后将该输入保存在生产代码中的 todoList 对象变量然后 第一个待办事项元素出现 在 UI 第二次点击 第三次点击出现第二个todoItem第四次点击第三个元素等等...我不知道这是为什么发生了,我想解决这个问题,这是我的代码:

----------APP COMPONENT-------
import React, { useState } from "react";
import Form from "./Form";
import List from "./List";

function App() {
  const [input, setInput] = useState("");
  const [todoItem, setTodoItem] = useState({});
  const [todoList, setTodoList] = useState([]);

  const addTodoItem = (e) => {
    e.preventDefault();
    input && setTodoItem({ content: input, id: Math.random() * 1000 });
    setTodoList([...todoList, todoItem]);
    setInput("");
  };

  return (
    <div>
      <Form input={input} addTodoItem={addTodoItem} setInput={setInput} />
      <List todoList={todoList} setTodoList={setTodoList} />
    </div>
  );
}

export default App;

---------FORM COMPONENT-----------
import React from "react";

function Form({ input, setInput, addTodoItem }) {
  return (
    <div>
      <form action="">
        <label>todos</label>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="what needs to be done"
          type="text"
        />
        <button onClick={addTodoItem}>set todo</button>
      </form>
    </div>
  );
}
export default Form;


----------LIST COMPONENT-----------
import React, { useState } from "react";

function List({ setTodoList, todoList }) {
  const [completed, setCompleted] = useState(false);

  const deleteItem = (del) => {
    todoList?.map((item, i, arr) => {
      setTodoList(arr.filter((value) => value.id != del));
      console.log("item>>>", item, "index>>>>", i, "array>>>>", arr);
    });
  };
  const reverseCompleted = () =>
    completed ? setCompleted(false) : setCompleted(true);
  return (
    <div>
      <div>
        {todoList.map((item, i) => (
          <div key={i}>
            <p>{item.content}</p>
            <button onClick={() => deleteItem(item.id)}>Delete</button>
            <button onClick={reverseCompleted}>
              {completed ? "unCompleted" : "Completed"}
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

export default List;

第一次尝试的内容是空的,因为 React 的 useState 更新 isn't immediate。因此,您将不得不等待 todoItem 更改才能将其放置在 todoList 数组中。

一种可能的解决方案是使用 React 的 useEffect 并跟踪 todoItem 上的更改。当它发生变化并且其 content 属性 不为空时,您可以更新 todoList:

useEffect(() => {
  if (todoItem.content) {
    setTodoList([...todoList, todoItem]);
  }
}, [todoItem]);

const addTodoItem = (e) => {
  e.preventDefault();
  input && setTodoItem({ content: input, id: Math.random() * 1000 });
  setInput("");
};

这个解决方案可能会被证明是容易出错的,因为每当 todoItem 引用发生变化时,它将被添加到 todoList,这不是您想要的。

我建议的另一个更好的解决方案是在 addTodoItem 处理程序中声明一个新变量,并将其附加到 todoList 数组,并调用 setTodoList

const addTodoItem = (e) => {
  e.preventDefault();
  const newTodoItem = { content: input, id: Math.random() * 1000 };
  input && setTodoItem(newTodoItem);
  setTodoList([...todoList, newTodoItem]);
  setInput("");
};