Home > database >  ReactJS context-api won't render after I get data
ReactJS context-api won't render after I get data

Time:01-06

This is a next.js site, since both my Navbar component and my cart page should have access to my cart's content I created a context for them. If I try to render the page, I get:

Unhandled Runtime Error

TypeError: Cannot read properties of undefined (reading 'key')

obs: The cartContent array exists and has length 1, I can get it by delaying when the data's rendered by using setTimeout, but, can't get it to render right after it's fetched.

I need to make it render after the data from firebase is returned, but always met with the mentioned error.

This is my _app.tsx file

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

This file has the startContext function that returns the context so it can be used.

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

This file contains the getCart function.

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

This is the cart.tsx webpage. When I load it I get the mentioned error.

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

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

I've tried to render the cart's content[0].key in many different ways, but couldn't do it. Always get error as if it were undefined. Doing a setTimeout hack works, but, I really wanted to solve this in a decent manner so it's at least error proof in the sense of not depending on firebase's response time/internet latency.

Edit: Since it works with setTimeout, it feels like a race condition where if setCartContent is used, it triggers the rerender but setCartContent can't finish before stuff is rendered so it will consider the state cartContent as undefined and won't trigger again later.

CodePudding user response:

Try changing

{cartContent && cartContent[0].key}

to

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

Edit:: The actual problem is in getCart function in line

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

This is either set to an empty array or an empty object. So try changing your if (new_cart) condition to

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

Now you wont get the undefined error

CodePudding user response:

Since there seemed to be a race condition, I figured the setCartContent was executing before its content was fetched. So I changed in the getCart function the map loop with an async function for a for loop

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

to

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

I can't make a map function with await in it without making it asynchronous so I the for loop made it work. Hope someone finds some alternatives to solving this, I could only come up with a for loop so the code is synchronous.

  •  Tags:  
  • Related