如何在多个挂钩中等待多个状态更新?

时间:2019-10-14 12:48:20

标签: reactjs react-hooks

示例

在我的场景中,我有一个带有过滤器的侧栏。每个过滤器都是由一个钩子创建的:

list

除其他外,自定义钩子还返回当前选择的值,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()函数:

reset()

const clearFilters = () => { const filterValues = Object.values(filters); for (const filter of filterValues) { filter.reset(); } }; 函数在每个过滤器中触发状态更新(当然是异步的)以重置所有选定的过滤器。

reset()

重置后立即,我想用重置/更新后的值做事,而不使用状态更新前的值做事,例如使用重置的过滤器调用API:

// setSelected is the setter comming from the return value of a useState statement
const reset = () => setSelected(initialSelected);

在这种情况下,使用旧值调用API(在clearFilters(); callAPI(); 中进行更新之前) 那么我如何等待所有过滤器完成更新后的状态呢?我的代码结构不好吗?我在监督什么吗?

对于单状态更新,我可以简单地使用reset(),但是在等待多个状态更新时,这确实很麻烦。

请不要以身作则,因为我在完全不同的情况下经常会遇到这个问题。

1 个答案:

答案 0 :(得分:0)

因此,我通过实现名为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状态更新:

 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();

是的,我可以等待状态更新!不幸的是,如果callAPI()依赖于更新的状态值,这不是全部。

const [filtersToApply, setFiltersToApply] = useState(/* ... */);

//...

const callAPI = ()  => {
   // filtersToApply will still contain old state here, although clearFilters() was "awaited"
   endpoint.getItems(filtersToApply); 
}

之所以会这样,是因为callAPI之后执行的await clearFilters();函数未重新呈现,因此它指向旧状态。但是有一个窍门,需要额外的useRef才能在清除过滤器后强制重新渲染:

 useEffect(() => {
    if (filtersCleared) {
      callAPI();
      setFiltersCleared(false);
    }
    // eslint-disable-next-line
  }, [filtersCleared]);

//...

const handleClearFiltersClick = async () => {
    await orderFiltersContext.clearFilters();
    setFiltersCleared(true);
};

这将确保在执行callAPI之前将其重新呈现。

就是这样!恕我直言,有点混乱,但它可以工作。

相关问题