开发者问题收集

从先前状态版本到钩子

2021-11-17
379

我试图操纵 api 响应,以便尽可能以更干净的方式呈现。

我有这个状态:

state = {
        tasks: {
          workingProgress: [],
          toDo: [],
          done: [],
        
        },
     };

从 api 我得到这样的对象:

{id: "1", task: "add button", team: "Metro", status: "workingProgress", …}
{id: "8", task: "fixed accessibility SR", team: "Metro", status: "toDo", …}

我的想法是将我的状态转换为这样的形式:

tasks: {
          workingProgress: [{id: "1", task: "add button", team: "Metro", ...},{}, {}],
          toDo: [{id: "8", task: "fixed accessibility SR", team: "Metro", ...}, {}, {}],
          done: [],
        
        },

我有这个并且可以很好地工作:

state = {
    tasks: {
      workingProgress: [],
      toDo: [],
      done: [],
    
    },
  };
  componentDidMount() {
    TasksAPI.getAll().then((res) => {
      this.setState(({ tasks }) => {
        res.forEach((item) => {
          if (tasks[item.status]) {
            tasks[item.status].push(item);
          }
        });
        return { tasks };
      });
    });
  }

我的问题是因为我打算从 钩子 更改“setState”代码。

const [tasks, setTasks] = useState({
    workingProgress: [],
    toDo: [],
    done: [],
  });
  const history = useHistory();

  useEffect(() => {
    TasksAPI.getAll().then((res) => {
      setTasks(({ tasks }) => {
        res.forEach((item) => {
          if (tasks[item.status]) {
            tasks[item.status].push(item);
          }
        });
        return { tasks };
      });
    });
  }, []);

但是当我尝试这个时,我的代码中断了:

Tasks.js:21 Uncaught (in promise) TypeError: Cannot read property 'toDo' of undefined

这是我的回报:

<div>
 {Object.entries(tasks).map(([key, status]) => (
   <div key={key}>
    <div >
     <ol>
      {status.map((item) => (
       <li>
        <div>
       ......

</div>

我的“钩子”方法不正确?

2个回答

我认为问题出在您的 useEffect 代码中,特别是 setTasks(({ task }) =>...return { task }; ,它们有两个主要问题:

  1. 在此代码中,您创建了一个本地 tasks 变量,该变量覆盖了 useState 返回的状态中的 tasks
  2. 您还试图破坏此状态并获取 tasks 属性,但这是错误的,因为您的状态已经是任务。您从之前的实现中继承了此逻辑,其中您的 state 是一个包含 tasks 属性的对象。

您的 useEffect 应按如下方式更改:

useEffect(() => {
    TasksAPI.getAll().then((res) => {
      setTasks((previousTasks) => {
        const newTasks = {...previousTasks}
        res.forEach((item) => {
          if (newTasks[item.status]) {
            newTasks[item.status] = [...newTasks[item.status], item]
          }
        });
        return newTasks;
      });
    });
  }, []);

newTasks = [...previousTasks]newTasks[item.status] = [...newTasks[item.status], item] 确保您不会改变先前的任务对象和状态数组并创建新的数组。

即使您的第一个解决方案(使用类)也没有解决这个问题,这可能会导致 React 更新出现错误。

但是,如果您不关心深度不​​变性,您可以将 forEach 中的代码简化为(虽然我不建议这样做):

if (newTasks[item.status]) {
    newTasks[item.status].push(item);
  }
fgkolf
2021-11-17

问题

据我所知,您遇到了一个简单的状态突变问题。效果钩子回调对 res 数组进行 for-each 操作,并直接推送到 tasks 状态,而不是创建新的数组和状态引用。

您还试图从 tasks 状态解构 tasks ,这显然不是定义的属性。当它是 this.state.tasks 并且“previousState”是 this.state 时,您可以解构 tasks ,但现在在 useState 钩子中 tasks 状态!然后,您返回一个对象 return { task }; ,该对象带有单个 tasks 属性,且实际状态嵌套得更深,例如使其成为 tasks.tasks.toDo

解决方案

无需循环并入队多个状态更新(这些更新必然需要从每个先前入队的更新中进行正确更新),只需循环并创建新的下一个状态对象并入队单个状态更新即可。

const [tasks, setTasks] = useState({
  workingProgress: [],
  toDo: [],
  done: [],
});

useEffect(() => {
  TasksAPI.getAll()
    .then((res) => {
      setTasks(tasks => res.reduce(
        (tasks, task) => ({
          ...tasks,                        // <-- shallow copy the state
          [task.status]: [
            ...(tasks[task.status] ?? []), // <-- shallow copy array
            task                           // <-- append new task item
          ],
        }),
        tasks,                             // <-- start from the previous state
      ));
  });
}, []);
Drew Reese
2021-11-17