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;
问题根源
sendRequest参数顺序不匹配:http.jsx中sendRequest的参数定义顺序是:url, method, body, reqIdentifier, reqExtra- 但
Ingredients.jsx调用时传参顺序是:url, method, body, reqExtra, reqIdentifier
这导致reqIdentifier拿到的是错误的值,Ingredients.jsx里的useEffect无法匹配到ADD_INGREDIENT或REMOVE_INGREDIENT,自然不会触发列表更新。
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 }), []);
验证效果
修复后,当执行添加或删除操作时:
sendRequest会正确传递reqIdentifier和reqExtra- 请求完成后,
httpState更新触发Ingredients.jsx中的useEffect useEffect匹配到正确的reqIdentifier,调用dispatch更新userIngredientsuserIngredients变化触发<IngredientList />自动重新渲染,列表实现自动更新
内容的提问来源于stack exchange,提问作者Martineli
相关产品推荐
相关产品推荐

