从 catch() 函数调用的 setSomeState() 不更新
setSomeState() called from a catch() function doesn't update
如果 React 代码有问题,出于某种原因,当我从 MyApp.promise().then(<here>)
更新 UI 而不是我的 MyApp.promise().then().catch(<here>)
[=23 时,一切都按预期工作=]
- 我知道代码实际执行到我实际调用
setData
的那一点,这是我的 useState()
返回的函数
- 在
then()
中调用该函数工作正常,但在 catch()
中不行
- 最终触发
catch()
的异常工作正常,因为 catch()
按预期执行
- 我在我的组件中添加了一个
console.log()
,我发现当更新来自 catch()
时它不再重新绘制
我想我的问题是:catch()
函数有什么特别之处,所以 React 不会运行?
这是处理升级状态更新的应用程序挂钩代码:
const useUpdateStatus = () => {
const [data,setData] = useState({status: STATUS.IDLE,changelog:null,tasks:[]})
const updateData = (d) => {
// We call setData with an anonymous function so we can merge previous
// data with new data
setData((prev) => {
console.log({ ...prev, ...d })
return { ...prev, ...d }
})
};
// Only once, we set the timer to periodically update status
useEffect(() => {
setInterval(() => {
MyApp.get('/system/upgrade')
.then((upgrade) => {
// If anything is not "pending", it means we are upgrading
for (var t of upgrade.tasks) {
if (t.status !== "pending") {
updateData({ status: STATUS.INSTALLING})
}
}
// updateData will call setData with the full status
// This works as intended, UI is updated on each step
updateData({ tasks: upgrade.tasks, changelog: upgrade.changelog})
})
.catch((e) => {
// If data can't be fetched, it probably means we are restarting
// services, so we updated the tasks array accordingly
setData((prev) => {
for (var t of prev.tasks) {
if (t['id'] === "restarting") {
t['status'] = 'running'
}
else if (t['status'] == "running") {
t['status'] = 'finished'
}
}
// The expected data is logged here
console.log(prev)
return prev
})
})
}, 1000);
},[])
return data
}
这是表示层:
// Using the hook :
const { changelog, tasks, status } = useUpdateStatus()
// Somewhere int he page :
<UpdateProgress tasks={tasks}/>
// The actual components :
const UpdateProgress = (props) => {
return(
<div style={{display: "flex", width: "100%"}}>
{ props.tasks.map(s => {
return(
<UpdateTask key={s.name} task={s}/>
)
})}
</div>
)
}
const UpdateTask = (props) => {
const colors = {
"pending":"LightGray",
"running":"SteelBlue",
"finished":"Green",
"failed":"red"
}
return(
<div style={{ textAlign: "center", flex: "1" }}>
<Check fill={colors[props.task.status]} width="50px" height="50px"/><br/>
<p style={props.task.status==="running" ? {fontWeight: 'bold'} : { fontWeight: 'normal'}}>{props.task.name}</p>
</div>
)
}
React 执行 Object.is
比较以检查状态更新调用后是否需要重新渲染。由于您正在改变 catch 块中的状态,因此会错误地通知 React 状态没有改变,因此不会触发重新渲染
您可以像下面这样更新您的状态以使其正常工作
.catch((e) => {
// If data can't be fetched, it probably means we are restarting
// services, so we updated the tasks array accordingly
setData((prev) => {
for (var t of prev.tasks) {
if (t['id'] === "restarting") {
t['status'] = 'running'
}
else if (t['status'] == "running") {
t['status'] = 'finished'
}
}
// The expected data is logged here
console.log(prev)
return {...prev}
})
})
然而,更新状态的更好方法是以不可变的方式进行
.catch((e) => {
// If data can't be fetched, it probably means we are restarting
// services, so we updated the tasks array accordingly
setData((prev) => ({
...prev,
tasks: prev.tasks.map((task) => {
if (task.id === "restarting") {
return { ...task, status: 'running'}
}
else if (task.id === "running") {
return { ...task, status: 'finished'}
}
return task
})
}))
})
如果 React 代码有问题,出于某种原因,当我从 MyApp.promise().then(<here>)
更新 UI 而不是我的 MyApp.promise().then().catch(<here>)
[=23 时,一切都按预期工作=]
- 我知道代码实际执行到我实际调用
setData
的那一点,这是我的useState()
返回的函数 - 在
then()
中调用该函数工作正常,但在catch()
中不行
- 最终触发
catch()
的异常工作正常,因为catch()
按预期执行 - 我在我的组件中添加了一个
console.log()
,我发现当更新来自catch()
时它不再重新绘制
我想我的问题是:catch()
函数有什么特别之处,所以 React 不会运行?
这是处理升级状态更新的应用程序挂钩代码:
const useUpdateStatus = () => {
const [data,setData] = useState({status: STATUS.IDLE,changelog:null,tasks:[]})
const updateData = (d) => {
// We call setData with an anonymous function so we can merge previous
// data with new data
setData((prev) => {
console.log({ ...prev, ...d })
return { ...prev, ...d }
})
};
// Only once, we set the timer to periodically update status
useEffect(() => {
setInterval(() => {
MyApp.get('/system/upgrade')
.then((upgrade) => {
// If anything is not "pending", it means we are upgrading
for (var t of upgrade.tasks) {
if (t.status !== "pending") {
updateData({ status: STATUS.INSTALLING})
}
}
// updateData will call setData with the full status
// This works as intended, UI is updated on each step
updateData({ tasks: upgrade.tasks, changelog: upgrade.changelog})
})
.catch((e) => {
// If data can't be fetched, it probably means we are restarting
// services, so we updated the tasks array accordingly
setData((prev) => {
for (var t of prev.tasks) {
if (t['id'] === "restarting") {
t['status'] = 'running'
}
else if (t['status'] == "running") {
t['status'] = 'finished'
}
}
// The expected data is logged here
console.log(prev)
return prev
})
})
}, 1000);
},[])
return data
}
这是表示层:
// Using the hook :
const { changelog, tasks, status } = useUpdateStatus()
// Somewhere int he page :
<UpdateProgress tasks={tasks}/>
// The actual components :
const UpdateProgress = (props) => {
return(
<div style={{display: "flex", width: "100%"}}>
{ props.tasks.map(s => {
return(
<UpdateTask key={s.name} task={s}/>
)
})}
</div>
)
}
const UpdateTask = (props) => {
const colors = {
"pending":"LightGray",
"running":"SteelBlue",
"finished":"Green",
"failed":"red"
}
return(
<div style={{ textAlign: "center", flex: "1" }}>
<Check fill={colors[props.task.status]} width="50px" height="50px"/><br/>
<p style={props.task.status==="running" ? {fontWeight: 'bold'} : { fontWeight: 'normal'}}>{props.task.name}</p>
</div>
)
}
React 执行 Object.is
比较以检查状态更新调用后是否需要重新渲染。由于您正在改变 catch 块中的状态,因此会错误地通知 React 状态没有改变,因此不会触发重新渲染
您可以像下面这样更新您的状态以使其正常工作
.catch((e) => {
// If data can't be fetched, it probably means we are restarting
// services, so we updated the tasks array accordingly
setData((prev) => {
for (var t of prev.tasks) {
if (t['id'] === "restarting") {
t['status'] = 'running'
}
else if (t['status'] == "running") {
t['status'] = 'finished'
}
}
// The expected data is logged here
console.log(prev)
return {...prev}
})
})
然而,更新状态的更好方法是以不可变的方式进行
.catch((e) => {
// If data can't be fetched, it probably means we are restarting
// services, so we updated the tasks array accordingly
setData((prev) => ({
...prev,
tasks: prev.tasks.map((task) => {
if (task.id === "restarting") {
return { ...task, status: 'running'}
}
else if (task.id === "running") {
return { ...task, status: 'finished'}
}
return task
})
}))
})