You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase练习应用首日触发Quota exceeded错误,求排查与解决

Firebase配额超限问题排查与useCallback使用疑问

我为练习Firebase开发了一个小型应用,但第一天就收到了“Quota exceeded”错误。我确定自己没发送那么多请求,肯定有导致请求激增的原因,但找不到问题所在。以下是相关代码:

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;

我是否需要用useCallback()包裹所有函数?


问题根源与解决方案

你的配额超限核心原因是App.js中useEffect的依赖项设置错误:

useEffect(() => {
  // 获取电影列表逻辑
}, [onSubmitMovie])

onSubmitMovie是每次组件渲染都会重新创建的新函数(未用useCallback包裹),导致这个useEffect会每次组件渲染都触发一次,反复调用getDocs请求Firestore,短时间内产生大量请求直接耗尽配额。

具体修复步骤:

  1. 修正useEffect依赖逻辑

    • 如果只需要初始化时获取一次电影列表,把依赖项改成空数组[]:
      useEffect(() => {
        const getMovieList = async () => {
          // 原有获取逻辑
        }
        getMovieList();
      }, [])
      
    • 如果需要在提交/删除/更新电影后刷新列表,直接在对应的操作函数执行成功后手动调用getMovieList,比如在onSubmitMovie里添加刷新逻辑,而非依赖错误的触发源。
  2. 按需使用useCallback,无需全包裹
    不需要给所有函数套useCallback,只需要包裹两类函数:

    • 作为useEffect依赖项的函数,比如onSubmitMovie:
      const onSubmitMovie = useCallback(async () => {
        try {
          await addDoc(moviesCollectionRef, { 
            title: newMovieTitle,
            releaseDate: newReleaseDate,
            recievedAnOscar: isNewMovieOscar,
            userId: auth?.currentUser?.uid 
          });
          // 提交成功后手动刷新列表
          const data = await getDocs(moviesCollectionRef);
          const filteredData = data.docs.map(doc => ({
            ...doc.data(),
            id: doc.id
          }))
          setMovieList(filteredData);
        } catch (err) {
          console.error(err);
        }
      }, [newMovieTitle, newReleaseDate, isNewMovieOscar, moviesCollectionRef])
      
    • 传递给子组件的函数(避免子组件不必要重渲染)。
      像deleteMovie、updateMovieTitle这类仅在点击时触发、不作为依赖的函数,完全不需要用useCallback包裹。
  3. 额外优化:Auth组件的状态监听
    Auth.js里的useEffect依赖auth?.currentUser?.email不够可靠,换成Firebase官方的onAuthStateChanged监听用户状态更稳定:

    import { onAuthStateChanged } from 'firebase/auth';
    // ...其他代码
    useEffect(() => {
      const unsubscribe = onAuthStateChanged(auth, (user) => {
        console.log(user?.email);
      });
      return unsubscribe; // 组件卸载时取消监听
    }, [])
    

内容的提问来源于stack exchange,提问作者Akio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:05:18