【问题标题】:setSomeState() called from a catch() function doesn't update从 catch() 函数调用的 setSomeState() 不会更新
【发布时间】:2021-04-05 17:30:15
【问题描述】:

如果反应代码有问题,由于某种原因,当我从 MyApp.promise().then(<here>) 更新 UI 时,一切都按预期工作,但在我的 MyApp.promise().then().catch(<here>) 中却没有

  • 我知道代码实际上已执行到我实际调用 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>
    )
}

【问题讨论】:

    标签: reactjs fetch-api


    【解决方案1】:

    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
                    })
                }))
            })
    

    【讨论】:

    • 谢谢您,先生,它确实有效。如果我明白为什么!所以你说我正在改变状态:这是否意味着我正在返回一个新的、修改过的对象?即该对象的引用是不同的,并且由于原始没有被修改,所以反应不会重新渲染?
    • 我所说的突变是指您在同一引用处修改 statee 对象,对于状态更新,您应该创建一个新引用。请阅读有关 javascript 中的不变性
    • 哈,情况正好相反,更新需要是一个不同的对象,而我只是对返回的对象进行了变异。你拯救了我的一天,我一定会寻找一些不变性的文献!
    猜你喜欢
    • 2022-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-15
    • 2016-04-21
    • 2020-03-06
    • 2011-10-24
    相关资源
    最近更新 更多