[英]How to wait for multiple state updates in multiple hooks?
例子
在我的場景中,我有一個帶有過濾器的側邊欄。每個過濾器都是由一個鈎子創建的:
const filters = {
customerNoFilter: useFilterForMultiCreatable(),
dateOfOrderFilter: useFilterForDate(),
requestedDevliveryDateFilter: useFilterForDate(),
deliveryCountryFilter: useFilterForCodeStable()
//.... these custom hooks are reused for like 10 more filters
}
除其他外,自定義鈎子返回當前選定的值、 reset()
和處理程序,如onChange
、 onRemove
。 (所以它不僅僅是隱藏在自定義鈎子后面的簡單useState
,請記住這一點)
基本上reset()
函數如下所示:
我還實現了一個 function 來清除所有為每個過濾器調用reset()
function 的過濾器:
const clearFilters = () => {
const filterValues = Object.values(filters);
for (const filter of filterValues) {
filter.reset();
}
};
reset()
function 正在觸發每個過濾器中的 state 更新(當然是異步的)以重置所有選定的過濾器。
// setSelected is the setter comming from the return value of a useState statement
const reset = () => setSelected(initialSelected);
在重置之后,我想使用重置/更新的值而不是使用 state 更新之前的值進行處理,例如使用重置的過濾器調用 API:
clearFilters();
callAPI();
在這種情況下,使用舊值調用 API(在reset()
更新之前)那么我如何等待所有過濾器完成 state 更新? 我的代碼結構很糟糕嗎? 我在監督什么嗎?
對於單個 state 更新,我可以簡單地使用useEffect
但這在等待多個 state 更新時真的很麻煩。
請不要認真對待這個例子,因為我經常在完全不同的情況下遇到這個問題。
所以我想出了一個解決方案,實現了一個名為useStateWithPromise
的自定義鈎子:
import { SetStateAction, useEffect, useRef, useState } from "react";
export const useStateWithPromise = <T>(initialState: T):
[T, (stateAction: SetStateAction<T>) => Promise<T>] => {
const [state, setState] = useState(initialState);
const readyPromiseResolverRef = useRef<((currentState: T) => void) | null>(
null
);
useEffect(() => {
if (readyPromiseResolverRef.current) {
readyPromiseResolverRef.current(state);
readyPromiseResolverRef.current = null;
}
/**
* The ref dependency here is mandatory! Why?
* Because the useEffect would never be called if the new state value
* would be the same as the current one, thus the promise would never be resolved
*/
}, [readyPromiseResolverRef.current, state]);
const handleSetState = (stateAction: SetStateAction<T>) => {
setState(stateAction);
return new Promise(resolve => {
readyPromiseResolverRef.current = resolve;
}) as Promise<T>;
};
return [state, handleSetState];
};
這個鈎子將允許await
state 更新:
const [selected, setSelected] = useStateWithPromise<MyFilterType>();
// setSelected will now return a promise
const reset = () => setSelected(undefined);
const clearFilters = () => {
const promises = Object.values(filters).map(
filter => filter.reset()
);
return Promise.all(promises);
};
await clearFilters();
callAPI();
是的,我可以等待 state 更新! 不幸的是,如果callAPI()
依賴於更新的 state 值,這還不是全部。
const [filtersToApply, setFiltersToApply] = useState(/* ... */);
//...
const callAPI = () => {
// filtersToApply will still contain old state here, although clearFilters() was "awaited"
endpoint.getItems(filtersToApply);
}
這是因為在 await clearFilters await clearFilters();
之后執行的callAPI
function; is 沒有重新渲染,因此它指向舊的 state。 但是有一個技巧需要額外的useRef
在過濾器被清除后強制重新渲染:
useEffect(() => {
if (filtersCleared) {
callAPI();
setFiltersCleared(false);
}
// eslint-disable-next-line
}, [filtersCleared]);
//...
const handleClearFiltersClick = async () => {
await orderFiltersContext.clearFilters();
setFiltersCleared(true);
};
這將確保callAPI
在執行之前被重新渲染。
而已。 恕我直言有點亂,但它的工作原理。
如果您想了解更多有關此主題的信息,請隨時查看我的博客文章。
肯定有其他方法可以實現這一目標,但我想到了這個。 您可以擁有一個名為hasReset
的 state 變量,並在 clearFilters clearFilters()
之后將其設置為true
。 並且可以產生監聽hasReset
的效果,如果它是true
則運行callApi()
並將hasReset
設置為false
。
例子:
const [hasReset, setHasReset] = useState(false);
useEffect(() => {
if (hasReset) {
callApi();
setHasReset(false);
}
}, [hasReset]);
const clearFilters = () => {
const filterValues = Object.values(filters);
for (const filter of filterValues) {
filter.reset();
}
setHasReset(true);
};
要回答上述問題,如果您在自定義掛鈎中使用 useState,則無需擔心異步部分。 UseState 鈎子只是為每個渲染聲明一個變量,它與 class 組件內部的 this.setState 不同,它也會按順序運行。
這是相關文章https的鏈接://overreacted.io/why-do-hooks-rely-on-call-order/
順便說一句,我實際上也感到困惑,為什么我們需要這么多過濾器掛鈎,這似乎具有相似的邏輯。 為什么不將選項傳遞給一個過濾器掛鈎並在其中運行邏輯。 我還認為有一個管理 state 的減速器是一種更好的方法,你不希望那些鈎子有自己的 state 和價值回報
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.