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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 10:36:25