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

Async-Await无法自动更新React组件问题求助

问题:Async-Await封装的请求无法触发列表自动更新

我开发了一个支持列表增删操作的网页,在自定义Hook(http.jsx)中封装了sendRequest异步请求函数用于和服务器交互。请求本身能正常执行,但列表不会随请求完成自动更新,必须手动刷新页面才能看到变化。如果把async-await代码替换为fetch回调的方式,列表就能正常自动更新,因此确定问题出在该函数或其相关逻辑上。

需要实现的效果是:ingredients.jsx中的<IngredientList />组件,在http.jsx的async-await代码执行完成后自动更新渲染。


相关代码

http.jsx

import { useReducer, useCallback } from 'react';

const HTTP_ACTION = {
  SEND: `send`,
  RESPONSE: `response`,
  ERROR: `error`,
  CLEAR: `clear`
};
const { SEND, RESPONSE, ERROR, CLEAR } = HTTP_ACTION;

const initialState = {
  loading: false,
  error: null,
  data: null,
  extra: null,
  identifier: null
};

const httpReducer = (currentHttpState, action) => {
  switch (action.type) {
    case SEND: {
      return {
        loading: true,
        error: null,
        data: null,
        extra: null,
        identifier: action.identifier
      };
    }
    case RESPONSE: {
      return {
        ...currentHttpState,
        loading: false,
        data: action.responseData,
        extra: action.extra
      };
    }
    case ERROR: {
      return { loading: false, error: action.errorMessage };
    }
    case CLEAR: {
      return initialState;
    }
    default: {
      throw new Error(`HTTP Error: Should not be reached!`);
    }
  }
};

const useHttp = () => {
  const [httpState, dispatchHttp] = useReducer(httpReducer, initialState);

  const clear = () => useCallback(dispatchHttp({ type: CLEAR }), []);

  const sendRequest = useCallback(
    async (url, method, body, reqIdentifier, reqExtra) => {
      try {
        dispatchHttp({ type: SEND, identifier: reqIdentifier });

        const response = await fetch(url, {
          method,
          body,
          headers: {
            'Content-Type': 'application/json'
          }
        });

        const responseData = await response.json();

        dispatchHttp({
          type: RESPONSE,
          responseData,
          extra: reqExtra
        });
      } catch (error) {
        dispatchHttp({
          type: ERROR,
          errorMessage: `Something went wrong! ${error.message}`
        });
      }
    },
    []
  );

  return {
    isLoading: httpState.loading,
    data: httpState.data,
    error: httpState.error,
    sendRequest,
    reqExtra: httpState.extra,
    reqIdentifier: httpState.identifier,
    clear
  };
};

export default useHttp;

Ingredients.jsx

import { useReducer, useCallback, useEffect, useMemo } from 'react';

import IngredientForm from './IngredientForm';
import IngredientList from './IngredientList';
import ErrorModal from '../UI/ErrorModal';
import Search from './Search';
import useHttp from '../../hooks/http';

const INGREDIENT_ACTION = {
  SET: `set`,
  ADD: `add`,
  DELETE: `delete`
};
const { SET, ADD, DELETE } = INGREDIENT_ACTION;

const ingredientReducer = (currentIngredients, action) => {
  switch (action.type) {
    case SET: {
      return action.ingredients;
    }
    case ADD: {
      return [...currentIngredients, action.ingredient];
    }
    case DELETE: {
      return currentIngredients.filter(({ id }) => id !== action.id);
    }
    default:
      throw new Error(`Should not get there!`);
  }
};

const Ingredients = () => {
  const [userIngredients, dispatch] = useReducer(ingredientReducer, []);
  const {
    isLoading,
    error,
    data,
    sendRequest,
    reqExtra,
    reqIdentifier,
    clear
  } = useHttp();

  useEffect(() => {
    if (!isLoading && !error && reqIdentifier === `REMOVE_INGREDIENT`) {
      dispatch({ type: DELETE, id: reqExtra });
    } else if (!isLoading && !error && reqIdentifier === `ADD_INGREDIENT`) {
      dispatch({
        type: ADD,
        ingredient: { id: data.name, ...reqExtra }
      });
    }
  }, [data, reqExtra, reqIdentifier, isLoading, error]);

  const addIngredientHandler = useCallback(
    ingredient => {
      sendRequest(
        `https://db-training-2efa8-default-rtdb.firebaseio.com/ingredients.json`,
        `POST`,
        JSON.stringify(ingredient),
        ingredient,
        `ADD_INGREDIENT`
      );
    },
    [sendRequest]
  );

  const handleRemoveItem = useCallback(
    listIngredientId => {
      sendRequest(
        `https://db-training-2efa8-default-rtdb.firebaseio.com/ingredients/${listIngredientId}.json`,
        `DELETE`,
        null,
        listIngredientId,
        `REMOVE_INGREDIENT`
      );
    },
    [sendRequest]
  );

  const filteredIngredientsHandler = useCallback(filteredIngredients => {
    dispatch({ type: SET, ingredients: filteredIngredients });
  }, []);

  const ingredientList = useMemo(() => {
    return (
      <IngredientList
        ingredients={userIngredients}
        onRemoveItem={handleRemoveItem}
      />
    );
  }, [userIngredients, handleRemoveItem]);

  return (
    <div className="App">
      {error && <ErrorModal onClose={clear}>{error}</ErrorModal>}

      <IngredientForm
        onAddIngredient={addIngredientHandler}
        loading={isLoading}
      />

      <section>
        <Search onLoadIngredients={filteredIngredientsHandler} />
        {ingredientList}
      </section>
    </div>
  );
};

export default Ingredients;

问题根源

  1. sendRequest参数顺序不匹配:

    • http.jsx中sendRequest的参数定义顺序是:url, method, body, reqIdentifier, reqExtra
    • 但Ingredients.jsx调用时传参顺序是:url, method, body, reqExtra, reqIdentifier
      这导致reqIdentifier拿到的是错误的值,Ingredients.jsx里的useEffect无法匹配到ADD_INGREDIENT或REMOVE_INGREDIENT,自然不会触发列表更新。
  2. clear函数定义错误:
    http.jsx中的clear函数写法错误,useCallback应该包裹整个函数定义,而不是在函数内部调用。当前写法会导致clear无法正确触发dispatch,且每次调用都会重新创建回调。


修复方案

1. 修正sendRequest传参顺序

在Ingredients.jsx中调整两个调用处的参数顺序:

  • 添加食材的调用:
    原代码:

    sendRequest(
      `https://db-training-2efa8-default-rtdb.firebaseio.com/ingredients.json`,
      `POST`,
      JSON.stringify(ingredient),
      ingredient,
      `ADD_INGREDIENT`
    );
    

    修改为:

    sendRequest(
      `https://db-training-2efa8-default-rtdb.firebaseio.com/ingredients.json`,
      `POST`,
      JSON.stringify(ingredient),
      `ADD_INGREDIENT`,
      ingredient
    );
    
  • 删除食材的调用:
    原代码:

    sendRequest(
      `https://db-training-2efa8-default-rtdb.firebaseio.com/ingredients/${listIngredientId}.json`,
      `DELETE`,
      null,
      listIngredientId,
      `REMOVE_INGREDIENT`
    );
    

    修改为:

    sendRequest(
      `https://db-training-2efa8-default-rtdb.firebaseio.com/ingredients/${listIngredientId}.json`,
      `DELETE`,
      null,
      `REMOVE_INGREDIENT`,
      listIngredientId
    );
    

2. 修正clear函数定义

在http.jsx中,将clear函数改为:

const clear = useCallback(() => dispatchHttp({ type: CLEAR }), []);

验证效果

修复后,当执行添加或删除操作时:

  1. sendRequest会正确传递reqIdentifier和reqExtra
  2. 请求完成后,httpState更新触发Ingredients.jsx中的useEffect
  3. useEffect匹配到正确的reqIdentifier,调用dispatch更新userIngredients
  4. userIngredients变化触发<IngredientList />自动重新渲染,列表实现自动更新

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 22:55:26