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

Firestore集合数据获取时列表重复问题求助

问题描述

我正尝试从Firestore集合中获取文档,但每次在应用中添加文档后,应用内的列表都会出现重复项,而Firestore集合里只能看到该文档的一个ID。

添加数据的代码

import Sidebar from './../../components/sidebar/Sidebar';
import Navbar from './../../components/navbar/Navbar';
import "./new.scss"

import { useState } from 'react';
import DriveFolderUploadIcon from '@mui/icons-material/DriveFolderUpload';
import { addDoc, collection, serverTimestamp } from "firebase/firestore";
import { db } from '../../firebase';
import { useNavigate } from 'react-router-dom';

const New = ({inputs}) => {

  const [file, setFile] = useState("");
  const [data, setData] = useState({});
  const navigate = useNavigate();

  const handleInput = (e) =>{
    const id = e.target.id;
    const value = e.target.value

    setData({...data, [id]:value});
  }

  const handleAdd = async(e) => {
      e.preventDefault();
      await addDoc(collection(db, "clients"), {
        ...data,
        timeStamp: serverTimestamp(),
      });
      navigate("/users")
  }

  return (
    <div className='new'>
    <Sidebar/>
    <div className="newContainer">
    <Navbar/>
    <div className="top">
    <h1>Add New Client</h1>
    </div>
    <div className="bottom">
    <div className="left">
      <img src={file ? URL.createObjectURL(file): "https://icon-library.com/images/no-image-icon/no-image-icon-0.jpg"} alt=''/>
    </div>
    <div className="right">
    
    <form onSubmit={handleAdd}>
              <div className="formInput">
                <label htmlFor="file">
                  Image: <DriveFolderUploadIcon className="icon" />
                </label>
                <input
                  type="file"
                  id="file"
                  onChange={(e) => setFile(e.target.files[0])}
                  style={{ display: "none" }}
                />
              </div>

              {inputs.map((input) => (
                <div className="formInput" key={input.id}>
                  <label>{input.label}</label>
                  <input
                    id={input.id}
                    type={input.type}
                    placeholder={input.placeholder}
                    onChange={handleInput}
                  />
                </div>
              ))}
              <button type="submit">
                Send
              </button>
            </form>

    </div>
    </div>
    </div>
    </div>
  )
}

export default New

获取数据的代码

import { DataGrid } from "@mui/x-data-grid";
import "./datatable.scss";
import { userColumns } from "../../datatablesource";
import VisibilityIcon from '@mui/icons-material/Visibility';
import EditNoteIcon from '@mui/icons-material/EditNote';
import DeleteIcon from '@mui/icons-material/Delete';
import { Link } from "react-router-dom";
import { useEffect, useState } from "react";
import { Tooltip } from "@mui/material";

import { collection, getDocs } from "firebase/firestore";
import { doc, deleteDoc } from "firebase/firestore";
import {db} from "../../firebase"
  
const Datatable = () => {

  const [data, setData] = useState([]);

  let list = []
  useEffect(() =>{
    const fetchData = async () =>{
      const querySnapshot = await getDocs(collection(db, "clients"));
      querySnapshot.forEach((doc) => {
        list.push({ id: doc.id, ...doc.data()})
});
    setData(list);
    }
    fetchData();
  }, [])

  const handleDelete = async (id) => {
    try {
      await deleteDoc(doc(db, "clients", id));
      setData(data.filter((item) => item.id !== id));
    } catch (err) {
      console.log(err);
    }
  };

const actionColumn = [
  { field: "action", headerName: "Action", width: 150, renderCell:(params)=>{
    return (
      <div className="cellAction">

        <Link>
        <Tooltip title="View Client" placement="bottom">
        <div className="viewButton"><VisibilityIcon/></div>
        </Tooltip>
        </Link>

        <Link>
        <Tooltip title="Edit Client" placement="bottom">
        <div className="editButton"><EditNoteIcon/></div>
        </Tooltip>
        </Link>

        <Link>
        <Tooltip title="Delete Client" placement="bottom">
        <div className="deleteButton"  onClick={() => handleDelete(params.row.id)}><DeleteIcon/></div>
        </Tooltip>
        </Link>
        
      </div>
    )
  }}
]
  return (
        <div className="datatable">
            <div className="datatableTitle">
        Add New Client
        <Link to="/users/new" className="link">
          Add New
        </Link>
      </div>
      <DataGrid
        className="datagrid"
        rows={data}
        columns={userColumns.concat(actionColumn)}
        pageSize={9}
        rowsPerPageOptions={[9]}
        checkboxSelection
      />
    </div>
  )
}

export default Datatable

解决方案

问题根源在Datatable组件的list变量声明位置:它被定义在useEffect外部,属于组件渲染上下文变量,每次组件重新渲染时不会被重置,而是保留之前的内容。这导致每次执行fetchData时,都会往旧的list里追加新数据,最终出现重复项。

基础修复方案

把list的声明移到fetchData函数内部,确保每次获取数据时都从空数组开始:

useEffect(() =>{
  const fetchData = async () =>{
    // 每次请求都初始化空数组,避免累积旧数据
    let list = []
    const querySnapshot = await getDocs(collection(db, "clients"));
    querySnapshot.forEach((doc) => {
      list.push({ id: doc.id, ...doc.data()})
    });
    setData(list);
  }
  fetchData();
}, [])

优化方案:实时监听数据变化

使用Firestore的onSnapshot实时监听集合变化,代替单次获取的getDocs,这样列表会自动同步Firestore的新增/修改/删除操作,彻底避免重复问题:

import { collection, onSnapshot } from "firebase/firestore";

// ...其他代码

useEffect(() =>{
  // 建立实时监听
  const unsubscribe = onSnapshot(collection(db, "clients"), (querySnapshot) => {
    const list = [];
    querySnapshot.forEach((doc) => {
      list.push({ id: doc.id, ...doc.data() });
    });
    setData(list);
  });

  // 组件卸载时取消监听,避免内存泄漏
  return () => unsubscribe();
}, [])

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 05:02:02