网站搜索

typescript - 在获取数据后,ReactJS context-api 无法渲染


这是一个 next.js 网站,由于我的 Navbar 组件和购物车页面都应该能够访问我的购物车内容,所以我为它们创建了一个上下文。如果我尝试渲染页面,我会得到:

未处理的运行时错误:

类型错误:无法读取未定义的属性(读取 'key') :

注意:cartContent 数组存在且长度为 1,我可以通过使用 setTimeout 延迟数据渲染来获取它,但无法在数据获取后立即渲染它。

我需要在从 Firebase 返回数据后进行渲染,但总是遇到提到的错误。

这是我的 _app.tsx 文件

function MyApp({ Component, pageProps }) {
    // set user for context
    const userContext = startContext();

    return (
        <UserContext.Provider value = { userContext }>
            <Navbar />
            <Component {...pageProps} />
            <Toaster />
        </UserContext.Provider>
    );
}

export default MyApp

此文件包含 startContext 函数,该函数返回上下文,以便可以使用它。

export const startContext = () => {
    const [user] = useAuthState(auth);
    const [cart, setCart] = useState(null);
    const [cartContent, setCartContent] = useState(null);
    useEffect(() => {
        if (!user) {
            setCart(null);
            setCartContent(null);
        }
        else {
            getCart(user, setCart, setCartContent);
        }
    }, [user]);
    return { user, cart, setCart, cartContent, setCartContent };
}

此文件包含 getCart 函数。

export const getCart = async (user, setCart, setCartContent) => {
    if (user) {
        try {
            let new_cart = await (await getDoc(doc(firestore, 'carts', user.uid))).data();
            if (new_cart) { 
                let new_cartContent = []
                await Object.keys(new_cart).map(async (key) => {
                    new_cartContent.push({...(await getDoc(doc(firestore, 'products-cart', key))).data(), key: key});
                });
                console.log(new_cartContent);
                setCartContent(new_cartContent);
                console.log(new_cartContent);
                setCart(new_cart);
            }
            else {
                setCart(null);
                setCartContent(null);
            }
        } catch (err) {
            console.error(err);
        }
    }
}

这是 cart.tsx 网页。当我加载它时,会出现所提到的错误。

export default () => {
    const { user, cart, cartContent } = useContext(UserContext);

    return (
        <AuthCheck>
            <div className="grid grid-cols-1 gap-4">
                {cartContent && cartContent[0].key}
            </div>
        </AuthCheck>
    )
}

我尝试过用多种不同的方式来渲染 cart 的内容[0].key,但都没有成功。总是出现好像它未定义的错误。使用 setTimeout 的方法可以解决,但我真的希望以一种体面的方式来解决这个问题,这样至少在不依赖 Firebase 的响应时间或网络延迟的情况下不会出错。

编辑:由于它可以与 setTimeout 一起使用,这感觉像是一个竞争条件,如果使用 setCartContent,它会触发重新渲染,但 setCartContent 在内容被渲染之前无法完成,因此它会将状态 cartContent 视为未定义,并且以后不会再次触发。

答案#1

试着改变

{cartContent && cartContent[0].key}

到

{cartContent?.length > 0 && cartContent[0].key}

编辑::实际问题出现在 getCart 函数的那一行

let new_cart = await (await getDoc(doc(firestore, 'carts', user.uid))).data();

这要么设置为空数组,要么设置为空对象。所以尝试将你的 if (new_cart) 条件更改为

if (Object.keys(new_cart).length > 0) {

现在你不会再得到undefined错误

答案#2

由于似乎存在竞争条件,我认为 setCartContent 在其内容被获取之前就执行了。所以我在 getCart 函数中将 map 循环改为使用异步函数的 for 循环

await Object.keys(new_cart).map(async (key) => {
        new_cartContent.push({...(await getDoc(doc(firestore, 'products-cart', key))).data(), key: key});
});

到

for (const key of Object.keys(new_cart)) {
         new_cartContent.push({...(await getDoc(doc(firestore, 'products-cart', key))).data(), key: key});
}

我不能在 map 函数中使用 await 而不让它变成异步,所以我用了 for 循环才让它工作。希望有人能找到解决这个问题的其他方法,我只能想出用 for 循环,所以代码是同步的。