如何阻止Firebase Firestore在每次按键时添加新文档?
问题解决:仅在提交时向Firestore添加优惠券代码
核心问题原因
你的代码中docRef.add({...})直接写在组件的顶层函数体内,每次组件重新渲染(包括用户输入字符导致code状态更新时)都会执行这段代码,所以才会出现输入过程中多次添加文档的情况。
修复步骤
1. 提取提交处理函数,将Firestore写入逻辑移到其中
创建专门的表单提交处理函数,仅在用户点击提交按钮时执行Firestore添加操作,同时阻止表单默认刷新行为:
const handleSubmit = async (e) => { e.preventDefault(); // 阻止表单默认提交刷新页面 if (!code.trim()) return; // 空代码不提交 const user = firebase.auth().currentUser; if (!user) { console.log("用户未登录"); return; } try { const docRef = firebase.firestore().collection('users').doc(user.uid).collection("codes"); await docRef.add({ code: code.trim(), creation: firebase.firestore.FieldValue.serverTimestamp() }); setCode(""); // 提交后清空输入框 // 提交成功后更新已兑换代码总数 const db = getFirestore(app); const coll = collection(db, "users", user.uid, "codes"); const snapshot = await getCountFromServer(coll); setCodesTotal(snapshot.data().count); } catch (error) { console.error("提交代码失败:", error); } };
2. 修改表单的onSubmit绑定
将表单的onSubmit从错误的CodeEnter改为绑定上述handleSubmit函数:
<form onSubmit={handleSubmit}>
3. 移除顶层的docRef.add代码
删除组件函数体内直接执行的这段代码:
docRef.add({ code, creation: firebase.firestore.FieldValue.serverTimestamp() })
其他优化点
- Firebase初始化问题:代码中同时使用了两种初始化方式,建议将Firebase初始化逻辑移到单独的
firebase.js文件中,避免重复初始化,组件直接导入使用即可。 onAuthStateChanged清理函数:原清理函数写法错误,应调用监听返回的注销函数:useEffect(()=> { const unsubscribe = onAuthStateChanged(auth, (user) => { // ...原有逻辑 }) return () => unsubscribe(); // 正确注销监听 }, [])- 进度百分比计算错误:最后一个分支
codesTotal >=1000 && codesTotal <2000中,codePct = codesTotal / 500应改为codesTotal / 2000,否则进度条会超过100%。
修复后的完整组件代码
import React, { useEffect, useState } from 'react' import "firebase/compat/storage"; import firebase from 'firebase/compat/app'; import 'firebase/compat/auth'; import 'firebase/compat/firestore'; import { collection, getCountFromServer, getFirestore } from '@firebase/firestore'; import { onAuthStateChanged } from 'firebase/auth'; import { auth } from '../../firebase'; import ProgressBar from "@ramonak/react-progress-bar"; import "./gauge.css" require("firebase/compat/firestore") const CodeEnter = () => { const app = getFirestore(); const [code, setCode] = useState(""); const [codesTotal, setCodesTotal] = useState(0); useEffect(()=> { const unsubscribe = onAuthStateChanged(auth, async (user) => { if (user) { const db = getFirestore(app); const coll = collection(db, "users", user.uid, "codes"); const snapshot = await getCountFromServer(coll); setCodesTotal(snapshot.data().count); } else { console.log("用户未登录") } }) return () => unsubscribe(); }, [app]) const handleSubmit = async (e) => { e.preventDefault(); if (!code.trim()) return; const user = firebase.auth().currentUser; if (!user) { console.log("用户未登录"); return; } try { const docRef = firebase.firestore().collection('users').doc(user.uid).collection("codes"); await docRef.add({ code: code.trim(), creation: firebase.firestore.FieldValue.serverTimestamp() }); setCode(""); // 更新总数 const db = getFirestore(app); const coll = collection(db, "users", user.uid, "codes"); const snapshot = await getCountFromServer(coll); setCodesTotal(snapshot.data().count); } catch (err) { console.error("提交失败:", err); } }; let rewardsMessage = "" if (codesTotal > 0 && codesTotal < 25 ) { rewardsMessage = `Only ${25 - codesTotal} more codes until your next reward!` } else if (codesTotal >=25 && codesTotal < 50 ) { rewardsMessage = `Only ${50 - codesTotal} more codes until your next reward!` console.log(`Threshold of 25 codes reached for user`) } else if (codesTotal >50 && codesTotal < 100 ) { rewardsMessage = `Only ${100 - codesTotal} more codes until your next reward!` console.log(`Threshold of 50 codes reached for user`) } else if (codesTotal >100 && codesTotal < 250 ) { rewardsMessage = `Only ${250 - codesTotal} more codes until your next reward!` console.log(`Threshold of 100 codes reached for user`) } else if (codesTotal >250 && codesTotal < 500 ) { rewardsMessage = `Only ${500 - codesTotal} more codes until your next reward!` console.log(`Threshold of 250 codes reached for user`) } else if (codesTotal >500 && codesTotal < 1000 ) { rewardsMessage = `Only ${1000 - codesTotal} more codes until your next reward!` console.log(`Threshold of 500 codes reached for user`) } else if (codesTotal >1000 && codesTotal < 2000 ) { rewardsMessage = `Only ${2000 - codesTotal} more codes until your next reward!` console.log(`Threshold of 1000 codes reached for user`) } let codePct = 0 if (codesTotal > 0 && codesTotal < 25 ) { codePct = codesTotal / 25 } else if (codesTotal >=25 && codesTotal < 50 ) { codePct = codesTotal / 50 } else if (codesTotal >=50 && codesTotal < 100 ) { codePct = codesTotal / 100 } else if (codesTotal >=100 && codesTotal < 250 ) { codePct = codesTotal / 250 } else if (codesTotal >=250 && codesTotal < 500 ) { codePct = codesTotal / 500 } else if (codesTotal >=500 && codesTotal < 1000 ) { codePct = codesTotal / 1000 } else if (codesTotal >=1000 && codesTotal < 2000 ) { codePct = codesTotal / 2000 // 修复计算错误 } return ( <div className='sign-in-container'> <form onSubmit={handleSubmit}> <h1>Enter your code</h1> <input type="text" placeholder='Enter your Code' value={code} onChange={(e)=> {setCode(e.target.value)}} ></input> <button type="submit">Redeem Code</button> </form> <div> <p>{`Total Codes Redeemed: ${codesTotal}`}</p> <p>{rewardsMessage}</p> </div> <div style={{ width: '50%', justifyContent:'center', margin: 'auto' }}> <ProgressBar completed={Math.round(codePct * 100)} className="wrapper" barContainerClassName="container" labelClassName="label" bgColor='#008000' margin='2000' padding='2000' labelAlignment='right' borderRadius='2px' /> </div> </div> ) } export default CodeEnter;
内容的提问来源于stack exchange,提问作者1jacros
相关产品推荐
相关产品推荐

