How To Wait For Multiple State Updates In Multiple Hooks?

- 1 answer


In my scenario I have a sidebar with filters.. each filter is created by a hook:

const filters = {
  customerNoFilter: useFilterForMultiCreatable(),
  dateOfOrderFilter: useFilterForDate(),
  requestedDevliveryDateFilter: useFilterForDate(),
  deliveryCountryFilter: useFilterForCodeStable()
  //.... these custom hooks are reused for like 10 more filters 

Among other things the custom hooks return currently selected values, a reset() and handlers like onChange, onRemove. (So it's not just a simple useState hidden behind the custom hooks, just keep that in mind)

Basically the reset() functions looks like this:

I also implemented a function to clear all filters which is calling the reset() function for each filter:

const clearFilters = () => {
    const filterValues = Object.values(filters);
    for (const filter of filterValues) {

The reset() function is triggering a state update (which is of course async) in each filter to reset all the selected filters.

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

Right after the resetting I want to do stuff with the reseted/updated values and NOT with the values before the state update, e.g. calling API with reseted filters:


In this case the API is called with the old values (before the update in the reset()) So how can i wait for all filters to finish there state updated? Is my code just badly structured? Am i overseeing something?

For single state updates I could simply use useEffect but this would be really cumbersome when waiting for multiple state updates..

Please don't take the example to serious as I face this issue quite often in quite different scenarios..



So I came up with a solution by implementing a custom hook named 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>(

  useEffect(() => {
    if (readyPromiseResolverRef.current) {
      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>) => {
    return new Promise(resolve => {
      readyPromiseResolverRef.current = resolve;
    }) as Promise<T>;

  return [state, handleSetState];

This hook will allow to await state updates:

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

Yey, I can wait on state updates! Unfortunatly that's not all if callAPI() is relying on updated state values ..

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


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

This happens because the executed callAPI function after await clearFilters(); is is not rerendered thus it points to old state. But there is a trick which requires an additional useRef to force rerender after filters were cleared:

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


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

This will ensure that callAPI was rerendered before it is executed.

That's it! IMHO a bit messy but it works.

If you want to read a bit more about this topic, feel free to checkout my blog post.