Firebase批量创建/更新文档及集合切换技术求助
问题描述
- 需求:以Firebase默认集合数据为初始值,允许用户修改后批量保存到个人专属集合;用户保存自定义值后,后续加载时优先读取个人集合数据作为输入框占位符,不再读取默认集合。
- 当前问题:通过
data.map渲染了带输入框的表格,但点击保存时只能创建单个文档,无法批量提交所有输入框的修改值。
解决方案
1. 换个方式存修改值
别用单个变量存单个奶酪的名字和成本了,用一个对象把所有奶酪的修改后成本都装起来(比如{ "芝士A": "10", "芝士B": "15" }),这样能区分每个奶酪的修改值,避免单个状态搞混不同数据。
2. 先读自己的数据,没有再读默认的
先监听个人集合,如果里面有数据,就用这个数据当输入框的占位符;要是没有,再去读默认集合的初始值,这样保存后下次打开就直接用自己的数据了。
3. 批量提交所有修改
用Firebase的writeBatch一次性把所有修改都提交,不用一个个创建文档,这样就能批量保存所有输入框的值,还能保证操作的原子性(要么全成功,要么全失败)。
修改后的完整代码
import { useState, useEffect } from 'react'; import { collection, query, orderBy, onSnapshot, writeBatch, doc, serverTimestamp } from 'firebase/firestore'; import { useAuthContext } from './替换为你的AuthContext路径'; import { db } from './替换为你的Firebase配置路径'; export default function CheeseCostForm() { const { user } = useAuthContext(); // 个人专属集合引用 const personalColRef = collection(db, `users/${user.uid}/cheese`); // 默认集合引用 const defaultColRef = collection(db, '/default/cheeseCosts/cheeseCosts'); const defaultQuery = query(defaultColRef, orderBy('listOrder', 'asc')); // 存储所有奶酪的修改后成本:键为奶酪名称,值为修改后的成本 const [cheeseCosts, setCheeseCosts] = useState({}); // 最终展示的数据源(优先个人集合,无数据则用默认集合) const [displayData, setDisplayData] = useState([]); // 监听数据源:先查个人集合,无数据再查默认集合 useEffect(() => { const unsubscribePersonal = onSnapshot(personalColRef, (snapshot) => { const personalData = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); if (personalData.length > 0) { setDisplayData(personalData); // 初始化修改状态为个人数据的成本 const initialCosts = {}; personalData.forEach(item => { initialCosts[item.cheeseName] = item.cheeseCost; }); setCheeseCosts(initialCosts); return; // 有个人数据就不再监听默认集合 } // 无个人数据时监听默认集合 const unsubscribeDefault = onSnapshot(defaultQuery, (snapshot) => { const defaultData = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setDisplayData(defaultData); // 初始化修改状态为默认数据的成本 const initialCosts = {}; defaultData.forEach(item => { initialCosts[item.cheeseName] = item.cheeseCost; }); setCheeseCosts(initialCosts); }); return unsubscribeDefault; }); return unsubscribePersonal; }, [user.uid, personalColRef, defaultQuery]); // 单个输入框的修改事件处理 const handleCostChange = (cheeseName, value) => { setCheeseCosts(prev => ({ ...prev, [cheeseName]: value })); }; // 批量保存所有修改 const updateCheese = async (e) => { e.preventDefault(); const batch = writeBatch(db); // 遍历所有展示的数据,批量写入个人集合 displayData.forEach(item => { // 若未修改则保留原值 const finalCost = cheeseCosts[item.cheeseName] || item.cheeseCost; // 用奶酪名称作为文档ID,确保每个奶酪唯一对应一个文档 const docRef = doc(personalColRef, item.cheeseName); batch.set(docRef, { cheeseName: item.cheeseName, cheeseCost: finalCost, uid: user.uid, dateLastChanged: serverTimestamp() }, { merge: true }); // 合并写入,避免覆盖已存在的字段 }); await batch.commit(); alert('所有奶酪成本已更新'); }; return ( <form onSubmit={updateCheese}> <table> <thead> <tr> <th>奶酪名称</th> <th>当前成本</th> <th>修改成本</th> </tr> </thead> <tbody> {displayData.map(costs => ( <tr key={costs.id || costs.cheeseName}> <td>{costs.cheeseName}</td> <td>{costs.cheeseCost}</td> <td> <input type="text" maxLength="3" value={cheeseCosts[costs.cheeseName] || costs.cheeseCost} onChange={(e) => handleCostChange(costs.cheeseName, e.target.value)} placeholder={costs.cheeseCost} /> </td> </tr> ))} </tbody> </table> <button className="btn" type="submit">更新奶酪成本</button> </form> ); }
内容的提问来源于stack exchange,提问作者Apersu
相关产品推荐
相关产品推荐

