skip to Main Content

So, I created this small app to practice with Firebase and on the first day I get "Quota exceeded" error. I’m pretty sure that I didn’t send that much requests on my own, there must be something that causes so much requests, but I don’t know what. Here is the code:
Auth.js:

import { useEffect, useState } from 'react';
import { auth, googleProvider } from '../config/firebase'; 
import { createUserWithEmailAndPassword, signInWithPopup, signOut } from 'firebase/auth';

export const Auth = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  useEffect(() => {
    console.log(auth?.currentUser?.email);
  }, [auth?.currentUser?.email]);

  const signIn = async () => { 
    try {
      await createUserWithEmailAndPassword(auth, email, password);
    } catch (err) {
      console.error(err);
    }
  }

  const signInWithGoogle = async () => { 
    try {
      await signInWithPopup(auth, googleProvider);
    } catch (err) {
      console.error(err);
    }
  }

  const logout = async () => {
    try {
      await signOut(auth);
    } catch (err) {
      console.error(err);
    }
  }

  return (
    <div>
      <input 
        type="email" 
        placeholder="Email..."
        onChange={(e) => setEmail(e.target.value)} />
      <input 
        type="password" 
        placeholder="Password..."
        onChange={(e) => setPassword(e.target.value)} />
      <button onClick={signIn}>Sign In</button>
      <button onClick={signInWithGoogle}>Sign In With Google</button>
      <button onClick={logout}>Log out</button>
    </div>
  )
}

App.js

import { useEffect, useMemo, useState } from 'react';
import { Auth } from './components/auth';
import { db, auth, storage } from './config/firebase'; 
import { ref, uploadBytes } from 'firebase/storage';
import { 
  getDocs, 
  collection, 
  addDoc, 
  deleteDoc,
  updateDoc,
  doc } from 'firebase/firestore'; 

import './App.css';

function App() {
  // movies from db collection
  const [movieList, setMovieList] = useState([]);
  const moviesCollectionRef = useMemo(() => collection(db, "movies"), []);
  
  // New movie states
  const [newMovieTitle, setNewMovieTitle] = useState('');
  const [newReleaseDate, setNewReleaseDate] = useState(0);
  const [isNewMovieOscar, setIsNewMovieOscar] = useState(false);

  // Update title state
  const [updatedTitle, setUpdatedTitle] = useState('');

  // File upload state
  const [fileUpload, setFileUpload] = useState(null);

  const onSubmitMovie = async () => {
    try {
      await addDoc(moviesCollectionRef, { 
        title: newMovieTitle,
        releaseDate: newReleaseDate,
        recievedAnOscar: isNewMovieOscar,
        userId: auth?.currentUser?.uid 
      });
    } catch (err) {
      console.error(err);
    }
  }

  const deleteMovie = async (id) => {
    const movieDoc = doc(db, "movies", id); 
    await deleteDoc(movieDoc);
  }

  const updateMovieTitle = async (id) => {
    const movieDoc = doc(db, "movies", id);
    await updateDoc(movieDoc, { title: updatedTitle }); 
  }
  
  useEffect(() => {
    const getMovieList = async () => {
      try {
        const data = await getDocs(moviesCollectionRef); 
        const filteredData = data.docs.map(doc => ({
          ...doc.data(),
          id: doc.id
        }))
        
        setMovieList(filteredData);
      } catch (err) {
        console.error(err);
      }
    }

    getMovieList();
  }, [onSubmitMovie])

  const uploadFile = async () => {
    if (!fileUpload) return;

    const filesFolderRef = ref(storage, `projectFiles/${fileUpload.name}`); 
    try {
      await uploadBytes(filesFolderRef, fileUpload);
    } catch (err) {
      console.error(err);
    }
  }

  return (
    <div className="App">
      <Auth />

      <div>
        <input 
          type="text" 
          placeholder='Movie title...'
          onChange={(e) => setNewMovieTitle(e.target.value)} />
        <input 
          type="number" 
          placeholder='Release date...'
          onChange={(e) => setNewReleaseDate(Number(e.target.value))} />
        <input 
          type="checkbox"
          checked={isNewMovieOscar}
          onChange={(e) => setIsNewMovieOscar(e.target.checked)} />
        <label>Recieved an Oscar</label>
        <button onClick={onSubmitMovie}>Submit movie</button>
      </div>

      <div>
        {movieList.map(movie => (
          <div key={movie.id}>
            <h1 style={{color: movie.recievedAnOscar ? 'green' : 'red'}}>{movie.title}</h1>
            <p>Date: {movie.releaseDate}</p>

            <button onClick={() => deleteMovie(movie.id)}>Delete movie</button>

            <input 
              type="text" 
              placeholder='New title...'
              onChange={(e) => setUpdatedTitle(e.target.value)} />
            <button onClick={() => updateMovieTitle(movie.id)}>Update Title</button>
          </div>
        ))}
      </div>

      <div>
        <input 
          type="file" 
          onChange={(e) => setFileUpload(e.target.files[0])} />
        <button onClick={uploadFile}>Upload File</button>
      </div>
    </div>
  );
}

export default App;

I don’t know, maybe I should wrap all my functions into useCallback() or what, there must be something to it. If you already encountered this problem, please help

2

Answers


  1. useEffect(() => {
      const getMovieList = async () => {
        // ...
      }
    
      getMovieList();
    }, [onSubmitMovie])
    

    Because you put onSubmitMovie in the dependency array, this effect will run every time the component renders. So you can very quickly rack up many many calls to get the data.

    There does not seem to be any reason for including that in the dependency array, since it’s never used. Change it to an empty array to only get the movies on mount

    useEffect(() => {
      const getMovieList = async () => {
        // ...
      }
    
      getMovieList();
    }, []);
    
    Login or Signup to reply.
  2. An empty dependency array is not best practice for each movie update (also you can manage this with global state for less data query) and is not allowed by default with the Next.js ESLint control, which will throw a warning for this. Instead, you should use appropriate trigger dependencies, such as when the form is ready to send, all fields are filled, etc. to fetch list from the database.

    Example code:

    ...
      const onSubmitMovie = async () => {
        if (!isFormFilled) return; //check all required inputs filled
    
        try {
          await addDoc(moviesCollectionRef, { 
            title: newMovieTitle,
            releaseDate: newReleaseDate,
            recievedAnOscar: isNewMovieOscar,
            userId: auth?.currentUser?.uid 
          });
          setMoviesUpdated(prev => !prev);
        } catch (err) {
          console.error(err);
        }
      }
    
      useEffect(() => {
        const getMovieList = async () => {
          try {
            const data = await getDocs(moviesCollectionRef); 
            const filteredData = data.docs.map(doc => ({
              ...doc.data(),
              id: doc.id
            }));
            
            setMovieList(filteredData);
          } catch (err) {
            console.error(err);
          }
        };
    
        getMovieList();
      }, [moviesUpdated]);
    ...

    Be sure your security settings are correct on firestore;

    https://firebase.google.com/docs/firestore/security/get-started

    Also, consider adding extra security layers for database update methods, such as server-side database connections and authentication APIs to the client, etc.

    https://nextjs.org/blog/security-nextjs-server-components-actions

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search