You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中使用useState更新对象不生效问题求助

问题:React Context无法保留多次API调用的合并结果

我用React Context存储一个关联camId的文件名对象,预期结构如下:

{
  cam1: [file1, file2, file3],
  cam2: [filea, fileb, filec]
  // ...更多camId对应的文件名数组
}

Context与Provider代码

export const FilenamesContext = createContext()

export const FilenamesProvider = ({ children }) => {
    const [filenamesObj, setFilenamesObj] = useState({})

    const addFilenames = ({ camId, incomingFilenamesArr }) => {
        let newFilenamesArr
        if (filenamesObj[camId]) {
            newFilenamesArr = combineAndSortArrays(filenamesObj[camId], incomingFilenamesArr)
        } else {
            newFilenamesArr = incomingFilenamesArr
        }
        console.log('newFilenamesArr :>> ', newFilenamesArr); // 这里打印的结果是正确的

        setFilenamesObj({ ...filenamesObj, [camId]: newFilenamesArr })
    }

    return (
        <FilenamesContext.Provider
            value={{
                filenamesObj,
                addFilenames,
            }}
        >
            {children}
        </FilenamesContext.Provider>
    )
}

展示组件代码

export default function CameraDetails() {
    let { camId } = useParams()
    let { filenamesObj } = useFilenames()

    return (
        <>
            <FilenamesLoader camId={camId} />
            <Typography>Cam file names {camId}</Typography>

            {JSON.stringify(filenamesObj)}

            <Link to={"/"}>Go HOME</Link>
        </>
    )
}

数据加载组件代码

export default function CameraLoader({ camId }) {
    const { filenamesObj, addFilenames } = useContext(FilenamesContext)

    useEffect(() => {
        async function getAllFilenames(camId, before, count = 1) {
            if (count > 5) {
                console.log("count reached 5") // 防止无限循环
                return
            }
            let returnedFilenames = await api.getFilenames({ camId, before })
            addFilenames({ camId, incomingFilenamesArr: returnedFilenames })

            await getAllFilenames(camId, getDateFromFilename(returnedFilenames[returnedFilenames.length - 1]), count + 1)
        }

        getAllFilenames(camId)
    }, [])

    return null
}

问题现象

Context中的filenamesObj无法保留历史值,每次API调用后只显示当前调用的结果,最终仅保留第五次API返回的数据,而非5次结果的合并。推测是setFilenamesObj调用时引用了过期的filenamesObj值,导致每次更新都是基于初始空对象展开,而非最新的上下文状态。


解决方案

问题根源在于addFilenames中使用了闭包捕获的旧状态值。当多次连续调用addFilenames时,每次调用的filenamesObj都是组件初始化时的空对象,因此每次更新都会覆盖之前的结果。

1. 改用函数式更新状态

修改setFilenamesObj为函数式更新,确保每次更新都基于最新的状态值:

const addFilenames = ({ camId, incomingFilenamesArr }) => {
    setFilenamesObj(prevState => {
        const newFilenamesArr = prevState[camId] 
            ? combineAndSortArrays(prevState[camId], incomingFilenamesArr)
            : incomingFilenamesArr;
        console.log('newFilenamesArr :>> ', newFilenamesArr);
        return { ...prevState, [camId]: newFilenamesArr };
    });
}

2. 稳定addFilenames函数引用

用useCallback包裹addFilenames,避免组件重渲染时生成新的函数引用:

export const FilenamesProvider = ({ children }) => {
    const [filenamesObj, setFilenamesObj] = useState({})

    const addFilenames = useCallback(({ camId, incomingFilenamesArr }) => {
        setFilenamesObj(prevState => {
            const newFilenamesArr = prevState[camId] 
                ? combineAndSortArrays(prevState[camId], incomingFilenamesArr)
                : incomingFilenamesArr;
            console.log('newFilenamesArr :>> ', newFilenamesArr);
            return { ...prevState, [camId]: newFilenamesArr };
        });
    }, []); // 依赖为空,内部使用函数式更新不依赖外部状态

    return (
        <FilenamesContext.Provider
            value={{
                filenamesObj,
                addFilenames,
            }}
        >
            {children}
        </FilenamesContext.Provider>
    )
}

3. 修正useEffect依赖项

在CameraLoader的useEffect中添加addFilenames作为依赖,确保使用最新的函数:

useEffect(() => {
    async function getAllFilenames(camId, before, count = 1) {
        if (count > 5) {
            console.log("count reached 5")
            return
        }
        let returnedFilenames = await api.getFilenames({ camId, before })
        addFilenames({ camId, incomingFilenamesArr: returnedFilenames })

        await getAllFilenames(camId, getDateFromFilename(returnedFilenames[returnedFilenames.length - 1]), count + 1)
    }

    getAllFilenames(camId)
}, [addFilenames])

以上修改后,每次状态更新都会基于最新的prevState,多次API调用的结果会被正确合并到filenamesObj中。


内容的提问来源于stack exchange,提问作者lowcrawler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 09:06:07