У меня есть объект useState
, который объявлен так в начале файла.
const [comments, setComments] = useState({
step_up: [], toe_walking: [],
toe_touches: [], squat: [],
side_to_side: [], rolling: [],
leg_lifts: [], hand_to_knees: [],
floor_to_stand: [], chair_elevation: [],
jumping_jacks: [], jump_rope: [],
bear_crawl: []
})
И в состоянии отдачи у меня есть множество comment
объектов. Я хочу назначить каждый комментарий соответствующей теме. Комментарий step_up
попадет в массив step_up
внутри объекта comments
. Я делаю это через useEffect
, но абсолютно ничего не происходит.
useEffect(() => {
let allComments = selectedClient.plan.comments
for( let i = 0; i < selectedClient.plan.comments.length; i ++) {
let tag = allComments[i].videoId
console.info(tag)
if (comments[tag]) {
let newArr = [...comments[tag]]
newArr.push(allComments[i])
let newObj = { ...comments }
newObj[tag] = newArr
setComments(comments => ({ ...newObj }))
}
}
setLoading(false)
}, [selectedClient])
Моя консоль подтверждает, что все videoId
действительны, поскольку журналы показывают следующее:
LOG step_up
LOG step_up
LOG bear_crawl
LOG bear_crawl
LOG jumping_jacks
LOG jump_rope
LOG hand_to_knees
LOG side_to_side
LOG chair_elevation
LOG floor_to_stand
LOG hand_to_knees
LOG chair_elevation
LOG squat
LOG leg_lifts
LOG rolling
LOG toe_touches
LOG toe_walking
LOG step_up
LOG step_up
LOG step_up
LOG step_up
LOG step_up
Всякий раз, когда я бросаю туда console.info(comments)
, он показывает мне, что весь объект просто заполнен пустыми массивами. Самая запутанная часть заключается в том, что каждый массив имеет ровно 1 комментарий внутри него к концу процесса. Но я понятия не имею, как эти комментарии туда попали, потому что каждый раз, когда я записываю комментарии, в них нет ничего, кроме пустых массивов. Что дает???
Проблема в том, что состояние comments
, на которое неоднократно ссылаются, является закрытием устаревшего состояния по сравнению с начальным состоянием. Каждый цикл перезаписывает предыдущее обновление состояния очереди циклов.
useEffect(() => {
let allComments = selectedClient.plan.comments
for( let i = 0; i < selectedClient.plan.comments.length; i ++){
let tag = allComments[i].videoId
console.info(tag)
if (comments[tag]) { // <-- (1) stale closure over initial state
let newArr = [...comments[tag]] // <-- (2) spreads empty array
newArr.push(allComments[i])
let newObj = { ...comments } // <-- (3) shallow copy initial state object
newObj[tag] = newArr
setComments(comments => ({ // <-- (4) shallow copy copy of state
...newObj
}))
}
}
setLoading(false)
}, [selectedClient])
Последнее обновление состояния в очереди — это то, которое вы, наконец, увидите при повторном рендеринге компонента.
Предлагается использовать обновление функционального состояния для правильного обновления любого предыдущего состояния вместо использования того, что закрыто в области обратного вызова.
useEffect(() => {
selectedClient.plan.comments.forEach(comment => {
const { videoId } = comment;
// Enqueue functional state update
setComments(comments => {
if (comments[videoId]) {
// State has matching property value, update state
return {
...comments,
[videoId]: comments[videoId].concat(comment);
};
} else {
// Nothing to update, return existing state
return comments;
}
});
});
setLoading(false);
}, [selectedClient]);
вы вызываете
setComments
внутри цикла, а не создаете новый объект состояния и вызываете его один раз, поэтому вы закрыли начальное состояниеcomments
для всех ваших вставок.