useState / useEffect 反应扩散运算符的错误

Bug with useState / useEffect react spread operator

我遇到了一个问题,我正在使用扩展运算符设置状态,但 useEffect 中的函数调用只会将从 API 中提取的最后一个值设置为状态。

// get all events to display on load
const getEvents = () => {
    console.log(props.id);
    axios.get(API.EVENTS.ROOT + props.id).then(
        res => {
            const data = res.data;
            setEvent(data);
            console.log(data);
            // event start and end dates
            setStart(new Date(data.start));
            setEnd(new Date(data.finish));
            // breakout dates
            for (let i = 0; i < data.breakouts.length; i++) {
                console.log(data.breakouts[i].start);
                data.breakouts[i].start = new Date(data.breakouts[i].start);
                data.breakouts[i].end = new Date(data.breakouts[i].end);
                console.log(data.breakouts[i].name, "test" + data.breakouts[i].start);
                // set date picker initial value 
                setFields({
                    ...fields,
                    [data.breakouts[i].name]: data.breakouts[i].start
                })
                console.log(fields);
            }

            setBreakoutRow(data.breakouts);
            console.log(breakoutRow);

        }
    ).catch(err => {
        console.log(err);
    })
}
// get events on load
useEffect(() => {
    console.log(props);
    getEvents();
}, []);

我将字段状态变量或 getEvents 函数添加为依赖项,它确实会更新所有值的字段 state,但会导致死循环。这为我提供了除最后一个以外的所有映射行中的空白日期。

另一项注意事项是,在页面加载后,可以通过函数调用毫无问题地更新字段对象,如下所示:

const handleDateChange = (dateName, dateValue) => {
        console.log(dateName, dateValue);
        setFields({
            ...fields,
            [dateName]: dateValue
        })
        console.log(fields);
    }

这让我相信我的 useEffect 钩子有问题,但我不确定哪里出了问题。

如果我没理解错的话,第一个例子中的 setState 有问题。原因是您在循环内调用 setState ,并且您试图传播旧状态,但是在循环完成之前状态没有设置,所以您必须积累您想要的一切在循环中放入你的状态并在最后设置它。

原因是您如何访问 previous 值以添加新元素:

for (let i = 0; i < data.breakouts.length; i++) {
  data.breakouts[i].start = new Date(data.breakouts[i].start);
  setFields({
    ...fields,
    [data.breakouts[i].name]: data.breakouts[i].start
  })
}

由于 fields 将仅在下一次渲染时更新,您正在向数组添加单个元素(但每个循环都是不同的元素)。

没有hooks/react也是一样:

const start = [];
for(let i = 0; i< 5; i++) console.log([...start, i]);

这将永远不会输出 [0,1,2,3,4]

你能做什么。

选项 1. 在中间变量中收集数据:

const temp = {};
for (let i = 0; i < data.breakouts.length; i++) {
  data.breakouts[i].start = new Date(data.breakouts[i].start);
  data.breakouts[i].end = new Date(data.breakouts[i].end);
  temp[data.breakouts[i].name] = data.breakouts[i].start
}
setFields({
  ...fields,
  ...temp
});

选项 2. 使用 setter 的功能版本作为累加器:

for (let i = 0; i < data.breakouts.length; i++) {
  data.breakouts[i].start = new Date(data.breakouts[i].start);
  setFields(({fields: prevFields}) => ({
    ...prevFields,
    [data.breakouts[i].name]: data.breakouts[i].start
  }))
}

我更喜欢第一个选项,原因有两个:

  1. 最终将数据转换移近调用点会更好,因此您需要一些 API.getMeaningfullThings 返回结构而不是 axios.get;所以重构更容易
  2. 也许更多的代码可以更好地显示正在发生的事情(这只是一个转换,而不是过滤或任何特定于 React 的东西)