Search code examples
reactjsfirebasegoogle-cloud-firestoregetuse-effect

Code making a lot of unnecessary Firestore Read Operations


Hi I am currently making a blog, which uses Firestore to store the blog posts then read and displayed in my home page. It also includes authentication through Firebase. For some reason I keep reaching the free daily quota of 50k reads, even though there are never more than a few documents stored and rarely any refreshing. The app is only available to me on my local hardware.

The amount of reads should be in the teens, but ends up being thousands for each refresh of the page.

The only times I actually connect to the database is when I use the UseEffect on the homepage so it must be that but im not sure what I'm doing wrong. When I had deletePost in the dependency it still was having the same effect.

Here is the Home page which is where UseEffect is used and reads from firestore to display the data.

import React, { useState, useEffect } from 'react';
import {getDocs, collection} from "firebase/firestore";
import {db} from "../../services/firebase";
import "./Home.css";
import MultiActionAreaCard from '../../components/postCard/postCard';
import { Grid } from '@mui/material';


function Home({isAuth}) {
    const [postList, setPostList] = useState([]);
    const postsCollectionRef = collection(db, "Posts");

    useEffect(() => {
        const getPosts = async () => {
            const data = await getDocs(postsCollectionRef);
            setPostList(data.docs.map((doc) => ({...doc.data(), id: doc.id})));
        }
        getPosts();
    }, [ postsCollectionRef]);

    
    return (
        <div className='homePage'>
            <Grid container spacing={3}>
            {postList.map((post) => {
                return (
                    <Grid item xs={12} sm={6} md={4}>
                        <MultiActionAreaCard title={post.title} author={post.author.name} authorID={post.author.id} post={post.postText} isAuth={isAuth} id={post.id}/>
                    </Grid>
            )})}
            </Grid>
        </div>
    )
};

export default Home;

Here is the deletePost function which is within the MultiActionAreaCard, used to connect to firestore and delete the post just in case. Only called when the delete button is clicked.

   const deletePost = async (id) => {
    const postDoc = doc(db, "Posts", id )
    await deleteDoc(postDoc);

if there's anything else you'd like me to include to help you all help me, please let me know! it won't let me post images since I don't have 10 reputations but most days it states I have 51k reads and only 2-3 writes and deletes. So, the other metrics are working as they should except read.


Solution

  • Without seeing what or how this Home component is rendered or anything else your app may be doing with your firestore, it seems likely that since postsCollectionRef is a new reference any time this Home component renders that this is triggering the useEffect hook callback also on each render.

    If nothing else references postsCollectionRef then move it either outside the component or into the useEffect's callback scope so it can be removed as an external dependency. This allows you to use an empty dependency so the useEffect hook callback is called only once when the Home component mounts.

    const [postList, setPostList] = useState([]);
    
    useEffect(() => {
      const postsCollectionRef = collection(db, "Posts");
    
      const getPosts = async () => {
        const data = await getDocs(postsCollectionRef);
        setPostList(data.docs.map((doc) => ({...doc.data(), id: doc.id})));
      }
    
      getPosts();
    }, []);
    

    If on the other hand postsCollectionRef is referenced elsewhere, then again, either move it outside the component or memoize it's value so it's a stable reference from render to render.

    const [postList, setPostList] = useState([]);
    const postsCollectionRef = useRef(collection(db, "Posts"));
    
    useEffect(() => {
      const getPosts = async () => {
        const data = await getDocs(postsCollectionRef.current);
        setPostList(data.docs.map((doc) => ({...doc.data(), id: doc.id})));
      }
    
      getPosts();
    }, []);
    
    // use postsCollectionRef.current elsewhere for accesses