Search code examples
firebasereact-hooksuse-effect

too many renders react hooks, useEffect, map


hey guys I'm trying to add to sum a simple product price for each iteration and notice its runs too manytimes. in the use effect I have to listen to the UserCart. can't do it with an empty array/[userCart]. also I get the following error in more components which i couldnt fix for the same reason with the last argument in the useEffect:

update: I did divide the useEffect as suggusted but it with [userCart ] it enter infinite loop

the code:

  import React, { useState, useEffect } from 'react'
    import firebase from 'firebase';
    import { useAuth, useStoreUpdate } from '../contexts/FirebaseContext';
    
        export default function Cart() {
            const [userMail, setUserMail] = useState(undefined)
            const [userCart, setUserCart] = useState(undefined)
            const [totalAmmout, setTotalAmmout] = useState(0)
            const user = useAuth()
            const userDoc = firebase.firestore().collection("cart").doc(userMail)
            const updateStore = useStoreUpdate()
        
            const updateCart = () => {
                userDoc.get().then((doc) => {
                    if (doc.exists) {
                        let cart = doc.data()
                        setUserCart(cart)
                    }
                })
            }
        
            async function removeFromCart(itemId, name, url, price, category, type, description) {
                const cartItem = { itemId, name, url, price, category, type, description }
                await userDoc.update({
                    item: firebase.firestore.FieldValue.arrayRemove(cartItem)
                })
                await updateCart()
                await updateStore()
            }
        
            useEffect(() => {
                if (user.currentUser) {
                    setUserMail(user.currentUser.email)
                    updateCart()
                    updateStore()
        
                }
            },userCart)
        
            if (!userCart) return <h1>hold</h1>
            let total = 0
            return (
                <main className="main-cart">
                    <div className="container">
                        {userCart.item && userCart.item.length >= 1 && userCart.item.map((item, i, arr) => {
                           
                            console.log(item);  //it runs more than 20 times
                            return (
                                < div className="item-container" key={item.itemId} >
                                    <h3>{item.name}</h3>
                                    <p>{item.price}</p>
                                    <img height="150px" width="150px" src={item.url} alt="" />
                                    <button onClick={async () => {
                                        await removeFromCart(item.itemId, item.name, item.url, item.price, item.category, item.type, item.description)
                                    }}>X</button>
                                </div>
                            )
                        })}
        
                    </div>
                    <div className="fixed-bottom-link">
                        <button>finish purchase</button>
                    </div>
                </main >
            )
        }

edit :

I found the major component, but still couldnt make it work the 2nd useEffect [userCart] wont run, it works only when not in the array, but it enter a loop:

import React, { useContext, useState, useEffect } from 'react';
import { auth } from '../firebase/firebase';
import firebase from '../firebase/firebase';

const FirebaseContext = React.createContext()
const StoreContext = React.createContext()
const StoreContextUpdate = React.createContext()

export function useAuth() {
    return useContext(FirebaseContext)
}
export function useStore() {
    return useContext(StoreContext)
}

export function useStoreUpdate() {
    return useContext(StoreContextUpdate)
}

export function AuthProvider({ children }) {
    const [currentUser, setCurrentUser] = useState();
    const [loading, setLoading] = useState(true)
    const [userMail, setUserMail] = useState(undefined)
    const [userCart, setUserCart] = useState(undefined)
    const userDoc = firebase.firestore().collection("cart").doc(userMail)

    const updateCart = () => {
        userDoc.get().then((doc) => {
            if (doc.exists) {
                let cart = doc.data()
                setUserCart(cart)
            }
        })
    }


    function signup(email, password) {
        return auth.createUserWithEmailAndPassword(email, password)
    }

    function login(email, pasword) {
        return auth.signInWithEmailAndPassword(email, pasword)
    }

    function logout() {
        return auth.signOut()
    }
    useEffect(() => {
        const unsubscribe = auth.onAuthStateChanged(async user => {
            await setCurrentUser(user)
            
            await setLoading(false)
        })
        return unsubscribe
    }, [userCart])

    useEffect(()=>{
        async function refreshCart(){
             await setUserMail(currentUser.email);
             await updateCart()   
         }
        if (currentUser && currentUser.email) {
               refreshCart()
           
        }
        return
    },[userCart])



    const value = {
        currentUser,
        signup,
        login,
        logout
    }
    return (
        <FirebaseContext.Provider value={value}>
            <StoreContext.Provider value={userCart && userCart.item}>
                <StoreContextUpdate.Provider value={updateCart}>
                    {!loading && children}
                </StoreContextUpdate.Provider>
            </StoreContext.Provider>
        </FirebaseContext.Provider>
    )

}


Solution

  •   useEffect(() => {
        if (user.currentUser) {
          setUserMail(user.currentUser.email);
          updateCart();
          updateStore();
        }
      }, [userCart]); //<-- userCart needs to be inside array
    

    and the reason for too many rerenders is you're calling updateCart function when the userCart changes and then userCart change will cause the call of updateCart again and this loop will repeat itself until you're out of memory

    I think adding another useEffect might help you

      useEffect(() => {
        if (user.currentUser) {
          setUserMail(user.currentUser.email);
          updateStore();
        }
      }, [userCart]); //<-- userCart needs to be inside array
    
      useEffect(() => {
        if (user.currentUser) {
          updateCart();
        }
      }, []);