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

如何阻止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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 05:08:11