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

如何无需刷新页面,实时渲染Firebase中的新增数据

问题描述

我正在开发一款简易笔记应用,需要实现创建新标签(LABEL)、在标签内创建标题(TITLE)并关联标签内容(TAGS)的功能。目前功能能正常运行,但提交新标签或新标题/内容后,必须刷新页面才能看到新增数据。我刚完成训练营学习,是个新手,恳请各位耐心解答:如何调用createLabel或createCard方法后,数据能立即在页面上渲染,无需刷新页面?

我的代码

import "./App.css";
import { useState, useEffect } from "react";
import React from "react";
// Components
import Header from "./components/Header/Header";
// Firebase
import { db } from "./firebase-config";
import {
  collection,
  getDocs,
  getDoc,
  setDoc,
  doc,
  updateDoc,
  addDoc,
  deleteDoc,
  arrayUnion,
  onSnapshot // 后续方案会用到这个
} from "firebase/firestore";
// Clipboard
import { CopyToClipboard } from "react-copy-to-clipboard";

function App() {
  const [newLabel, setNewLabel] = useState("");
  const [labels, setLabels] = useState([]);
  const [activeLabels, setActiveLabels] = useState("");
  const [name, setName] = useState("");
  const [tags, setTags] = useState("");
  const labelCollectionRef = collection(db, "labels");

  useEffect(() => {
    const getLabels = async () => {
      const data = await getDocs(labelCollectionRef);
      setLabels(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
    };

    getLabels();
  }, []);

  const createLabel = async () => {
    await setDoc(doc(db, "labels", newLabel), { id: newLabel });
  };

  const clickHandler = async (title) => {
    setActiveLabels(title);
  };

  const createCard = async () => {
    await updateDoc(doc(db, "labels", activeLabels), {
      info: arrayUnion({ name: name, tags: tags }),
    });
  };

  const deleteLabel = async (i) => {
    await deleteDoc(doc(db, "labels", activeLabels));
  };

  return (
    <div className="App">
      <Header />
      <main>
        <input
          onChange={(e) => {
            setNewLabel(e.target.value);
          }}
          placeholder="Enter Label Name"
          type="text"
        ></input>
        <button onClick={createLabel}>Create Label</button>

        {labels.map((label, i) => {
          if (activeLabels == label.id) {
            return (
              <>
                <div className="tags__section">
                  <h2
                    key={i}
                    className="tags__title"
                    onClick={() => clickHandler(label.id)}
                  >
                    Label: {label.id}
                    <button
                      onClick={() => deleteLabel(i)}
                      className="tags__delete"
                    >
                      DELETE
                    </button>
                  </h2>
                </div>
                {label.info &&
                  label.info.map((info, i) => {
                    return (
                      <div key={i}>
                        <h1 className="tags__name">Name: {info.name}</h1>
                        <h2 className="tags__tags" type="text">
                          Tags: {info.tags}
                        </h2>
                        <CopyToClipboard text={info.tags}>
                          <button className="tags__clipboard">COPY</button>
                        </CopyToClipboard>
                      </div>
                    );
                  })}
                <div className="tags__add">
                  <input
                    onChange={(e) => setName(e.target.value)}
                    type="text"
                    name="title"
                    placeholder="Add title..."
                  ></input>

                  <textarea
                    onChange={(e) => setTags(e.target.value)}
                    name="tags"
                    type="text"
                    placeholder="Add tags..."
                  ></textarea>
                  <button onClick={createCard}>Create Card</button>
                </div>
              </>
            );
          } else {
            return (
              <h2
                className="tags__title"
                onClick={() => clickHandler(label.id)}
                key={i}
              >
                Label: {label.id}
              </h2>
            );
          }
        })}
      </main>
    </div>
  );
}

export default App;
解决方案

有两种常用方法可以实现数据即时渲染,适合不同场景:

方法一:手动更新本地状态(简单直接,适合小项目)

在完成Firestore操作后,直接修改React的labels状态,不用重新拉取整个列表:

1. 修改createLabel函数

const createLabel = async () => {
  if (!newLabel.trim()) return; // 避免创建空标签
  await setDoc(doc(db, "labels", newLabel), { id: newLabel });
  // 把新标签添加到本地状态
  setLabels(prevLabels => [...prevLabels, { id: newLabel }]);
  setNewLabel(""); // 清空输入框
};

2. 修改createCard函数

const createCard = async () => {
  if (!name.trim() || !tags.trim() || !activeLabels) return; // 校验输入
  await updateDoc(doc(db, "labels", activeLabels), {
    info: arrayUnion({ name: name, tags: tags }),
  });
  // 更新当前激活标签的info数组
  setLabels(prevLabels => prevLabels.map(label => {
    if (label.id === activeLabels) {
      return {
        ...label,
        info: label.info ? [...label.info, { name, tags }] : [{ name, tags }]
      };
    }
    return label;
  }));
  setName(""); // 清空输入
  setTags("");
};

3. 修改deleteLabel函数

const deleteLabel = async () => {
  if (!activeLabels) return;
  await deleteDoc(doc(db, "labels", activeLabels));
  // 从本地状态移除删除的标签
  setLabels(prevLabels => prevLabels.filter(label => label.id !== activeLabels));
  setActiveLabels(""); // 重置激活状态
};

方法二:使用Firestore实时监听(更可靠,自动同步)

用Firestore的onSnapshot替代getDocs,它会实时监听集合变化,自动更新页面状态,不用手动修改:

修改useEffect代码

useEffect(() => {
  // 监听labels集合的所有变化
  const unsubscribe = onSnapshot(labelCollectionRef, (snapshot) => {
    const updatedLabels = snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id }));
    setLabels(updatedLabels);
  });

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

使用这种方法后,你不需要修改createLabel、createCard、deleteLabel的逻辑,Firestore会自动把最新数据推送到页面,甚至其他设备修改数据也能实时同步。

额外优化建议

  • 给输入框添加非空校验,避免无效操作
  • 操作完成后清空输入框,提升用户体验
  • 可以给按钮添加加载状态,防止重复点击

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 08:45:39