React+Firestore应用改造:实现成对食材录入与存储
改造React + Firestore应用:将单一食材输入改为成对食材存储
以下是针对需求的完整改造方案,从状态结构、输入逻辑到存储展示全流程调整:
1. 调整表单状态结构
将原来的字符串数组ingredients改为包含from/to字段的对象数组,默认初始化一组空配对:
const [form, setForm] = useState({ ingredients: [{ from: "", to: "" }], })
2. 修改输入框渲染逻辑
把单个输入框替换为「一组两个输入框」,分别对应from和to字段:
{form.ingredients.map((pair, i) => ( <div key={i} className="ingredient-pair"> <input type="text" placeholder="食材A (from)" value={pair.from} onChange={(e) => handleIngredientChange(e, i, "from")} /> <input type="text" placeholder="食材B (to)" value={pair.to} onChange={(e) => handleIngredientChange(e, i, "to")} /> </div> ))}
3. 更新输入处理函数
重写输入变更逻辑,针对对象的from/to字段单独更新:
const handleIngredientChange = (e, index, field) => { const updatedIngredients = [...form.ingredients]; updatedIngredients[index] = { ...updatedIngredients[index], [field]: e.target.value, }; setForm({ ...form, ingredients: updatedIngredients, }); };
4. 调整添加输入组的函数
点击添加按钮时,新增一组空的from/to对象,而非单个空字符串:
const handleAddIngredientPair = () => { setForm({ ...form, ingredients: [...form.ingredients, { from: "", to: "" }], }); };
5. 优化表单提交验证
验证每一组配对的from和to都不为空,避免无效数据存入Firestore:
const handleSubmit = (e) => { e.preventDefault(); // 校验所有配对的食材是否填写完整 const isValid = form.ingredients.every(pair => pair.from.trim() && pair.to.trim()); if (!isValid) { alert("请填写每一组的两个食材"); return; } addDoc(recipesCollectionRef, form); // 重置表单为一组空配对 setForm({ ingredients: [{ from: "", to: "" }], }); setPopupActive(false); };
6. 更新食材展示逻辑
展示时按配对格式渲染from和to:
{recipe.viewing && ( <div> <h4>食材配对</h4> <ul> {recipe.ingredients.map((pair, i) => ( <li key={i}> {pair.from} → {pair.to} </li> ))} </ul> </div> )}
完整改造后的核心代码片段
function App() { const [recipes, setRecipes] = useState([]); const [form, setForm] = useState({ ingredients: [{ from: "", to: "" }], }); const [popupActive, setPopupActive] = useState(false); const recipesCollectionRef = collection(db, "recipes"); useEffect(() => { onSnapshot(recipesCollectionRef, (snapshot) => { setRecipes( snapshot.docs.map((doc) => ({ id: doc.id, viewing: false, ...doc.data(), })) ); }); }, []); const handleView = (id) => { const recipesClone = [...recipes]; recipesClone.forEach((recipe) => { recipe.viewing = recipe.id === id ? !recipe.viewing : false; }); setRecipes(recipesClone); }; const handleSubmit = (e) => { e.preventDefault(); const isValid = form.ingredients.every(pair => pair.from.trim() && pair.to.trim()); if (!isValid) { alert("请填写每一组的两个食材"); return; } addDoc(recipesCollectionRef, form); setForm({ ingredients: [{ from: "", to: "" }] }); setPopupActive(false); }; const handleIngredientChange = (e, index, field) => { const updatedIngredients = [...form.ingredients]; updatedIngredients[index] = { ...updatedIngredients[index], [field]: e.target.value, }; setForm({ ...form, ingredients: updatedIngredients }); }; const handleAddIngredientPair = () => { setForm({ ...form, ingredients: [...form.ingredients, { from: "", to: "" }], }); }; return ( <div className="App"> {/* 弹窗表单 */} {popupActive && ( <div className="popup"> <form onSubmit={handleSubmit}> <h3>添加食材配对</h3> {form.ingredients.map((pair, i) => ( <div key={i} className="input-group"> <input type="text" placeholder="食材A" value={pair.from} onChange={(e) => handleIngredientChange(e, i, "from")} /> <span>→</span> <input type="text" placeholder="食材B" value={pair.to} onChange={(e) => handleIngredientChange(e, i, "to")} /> </div> ))} <button type="button" onClick={handleAddIngredientPair}>添加一组食材</button> <button type="submit">提交</button> </form> </div> )} {/* 食谱列表 */} {recipes.map((recipe) => ( <div key={recipe.id} className="recipe-card"> <button onClick={() => handleView(recipe.id)}> {recipe.viewing ? "收起" : "查看食材"} </button> {recipe.viewing && ( <div> <h4>食材配对</h4> <ul> {recipe.ingredients.map((pair, i) => ( <li key={i}>{pair.from} → {pair.to}</li> ))} </ul> </div> )} </div> ))} </div> ); }
内容的提问来源于stack exchange,提问作者Sidhant Kumar
相关产品推荐
相关产品推荐

