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,短时间内产生大量请求直接耗尽配额。
具体修复步骤:
修正useEffect依赖逻辑
- 如果只需要初始化时获取一次电影列表,把依赖项改成空数组
[]:useEffect(() => { const getMovieList = async () => { // 原有获取逻辑 } getMovieList(); }, []) - 如果需要在提交/删除/更新电影后刷新列表,直接在对应的操作函数执行成功后手动调用
getMovieList,比如在onSubmitMovie里添加刷新逻辑,而非依赖错误的触发源。
- 如果只需要初始化时获取一次电影列表,把依赖项改成空数组
按需使用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包裹。
- 作为useEffect依赖项的函数,比如
额外优化: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
相关产品推荐
相关产品推荐

