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

React表单双向绑定:编辑按钮传值预填充表单遇无限循环问题

解决React食谱项目编辑功能的无限循环问题

核心问题分析

你之前尝试实现编辑功能时出现无限循环,主要原因有两个:

  • 传递给子组件的回调函数未用useCallback包裹,导致父组件每次渲染都生成新函数,触发子组件重复渲染
  • 原有useEffect依赖项包含isLoading/isDeleting,状态更新会反复触发数据请求,形成循环

分步修改实现编辑功能并解决循环问题

1. 改造App.js,添加编辑状态与回调

在App组件中新增存储编辑食谱的状态,同时优化原有useEffect的依赖项:

function App() {
  // 新增:存储待编辑的食谱数据
  const [editingRecipe, setEditingRecipe] = useState(null);
  const [user, setUser] = useState({});
  const [recipes, setRecipes] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [isDeleting, setIsDeleting] = useState(false);

  // ... 原有登录/登出逻辑保持不变

  // 新增:接收RecipeTable传来的编辑数据
  const startEditHandler = useCallback((recipe) => {
    setEditingRecipe(recipe);
  }, []);

  // 修复:移除isLoading/isDeleting依赖,避免无限触发请求
  useEffect(() => {
    fetchRecipesHandler();
    setIsLoading(false);
    setIsDeleting(false);
  }, [fetchRecipesHandler]);

  // 新增:编辑完成后更新列表并清空编辑状态
  const finishEditHandler = useCallback(() => {
    setEditingRecipe(null);
    setIsLoading(true);
  }, []);

  return (
    <div className="App">
      {/* ... 原有登录模块保持不变 */}
      {user && <>
        {/* 传递编辑数据和完成回调给RecipeForm */}
        <RecipeForm 
          onAddRecipe={addRecipeHandler} 
          editingRecipe={editingRecipe}
          onFinishEdit={finishEditHandler}
        />
        {/* 传递编辑回调给RecipeTable */}
        <RecipeTable 
          recipeData={recipes} 
          onIsDelete={() => setIsDeleting(true)}
          onStartEdit={startEditHandler}
        />
      </>}
    </div>
  );
}

2. 给RecipeTable的编辑图标绑定点击事件

修改RecipeTable组件,给PencilFill添加点击事件,传递当前行的食谱数据:

function RecipeTable(props) {
  // ... 原有状态和删除逻辑保持不变

  return (
    <Container>
      {/* ... 原有表格头部保持不变 */}
      <tbody>
      {props.recipeData.map((item) => (
          <tr key={item.id}>
            {/* ... 原有表格内容保持不变 */}
            <td>
              {/* 绑定编辑点击事件,传递当前食谱 */}
              <PencilFill 
                className="m-2" 
                color="black"
                onClick={() => props.onStartEdit(item)}
              ></PencilFill>
              <Trash className="m-2" onClick={() => removeRecipeHandler(item.id)} color="red" />
            </td>
          </tr>
      ))}
      </tbody>
    </Container>
  )
}

3. 修改RecipeForm,支持预填充编辑数据

添加useEffect监听编辑数据变化,自动填充表单,同时区分新增/编辑提交逻辑:

function RecipeForm(props) {
  const [enteredRecipe, setEnteredRecipe] = useState('');
  const [enteredIngredients, setEnteredIngredients] = useState('');
  const [enteredNotes, setEnteredNotes] = useState('');
  const [enteredOnlineLink, setOnlineEnteredLink] = useState('');
  const [ingredientList, setIngredientList] = useState([]);

  // 监听编辑数据变化,自动填充表单
  useEffect(() => {
    if (props.editingRecipe) {
      const { recipeName, ingredients, notes, onlineLink } = props.editingRecipe;
      setEnteredRecipe(recipeName);
      setIngredientList(ingredients || []);
      setEnteredNotes(notes);
      setOnlineEnteredLink(onlineLink);
    } else {
      // 无编辑数据时清空表单
      setEnteredRecipe('');
      setIngredientList([]);
      setEnteredNotes('');
      setOnlineEnteredLink('');
    }
  }, [props.editingRecipe]);

  // ... 原有输入变化、添加/删除配料逻辑保持不变

  // 修改提交逻辑,区分新增和编辑
  const submitHandler = (event) => {
    event.preventDefault();

    const recipeData = {
      recipeName: enteredRecipe,
      ingredients: ingredientList,
      notes: enteredNotes,
      onlineLink: enteredOnlineLink
    };

    if (props.editingRecipe) {
      // 编辑逻辑:发送PUT请求更新数据
      fetch(`https://react-demo-65577-default-rtdb.firebaseio.com/recipes/${props.editingRecipe.id}.json`, {
        method: 'PUT',
        body: JSON.stringify(recipeData),
        headers: {
          'Content-Type': 'application/json'
        }
      }).then(() => {
        props.onFinishEdit();
      }).catch(err => console.log(err));
    } else {
      // 原有新增逻辑
      props.onAddRecipe(recipeData);
    }

    // 清空输入框
    setEnteredRecipe('');
    setEnteredIngredients('');
    setEnteredNotes('');
    setOnlineEnteredLink('');
  };

  // ... 原有表单渲染保持不变
}

4. 修复删除逻辑的潜在问题

优化removeRecipeHandler,避免依赖内部状态触发不必要的重渲染:

const removeRecipeHandler = useCallback(async (recipeId) => {
  try {
    const response = await fetch(`https://react-demo-65577-default-rtdb.firebaseio.com/recipes/${recipeId}.json`, {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json'
      }
    });

    if (!response.ok) {
      throw new Error('Something went wrong');
    }

    // 直接通知父组件更新,无需依赖内部isDeleted状态
    props.onIsDelete(true);
  } catch (error) {
    console.log(error);
  }
}, [props]);

关键优化点

  • 所有传递给子组件的回调用useCallback包裹,避免不必要的重渲染
  • 精简useEffect依赖项,只保留触发数据请求的必要条件
  • 用useEffect监听编辑状态变化,确保表单预填充逻辑稳定

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 10:37:15