Выполнение асинхронного кода при обновлении состояния с помощью ответных хуков

У меня есть что-то вроде:

const [loading, setLoading] = useState(false);

...

setLoading(true);
doSomething(); // <--- when here, loading is still false. 

Установка состояния по-прежнему асинхронна, так как лучше всего дождаться setLoading() вызова setLoading()?

setLoading(), кажется, не принимает обратный вызов, как setState() раньше.

пример

класс на основе

getNextPage = () => {
    // This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
    goToTop();

    if (this.state.pagesSeen.includes(this.state.page + 1)) {
      return this.setState({
        page: this.state.page + 1,
      });
    }

    if (this.state.prefetchedOrders) {
      const allOrders = this.state.orders.concat(this.state.prefetchedOrders);
      return this.setState({
        orders: allOrders,
        page: this.state.page + 1,
        pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
        prefetchedOrders: null,
      });
    }

    this.setState(
      {
        isLoading: true,
      },
      () => {
        getOrders({
          page: this.state.page + 1,
          query: this.state.query,
          held: this.state.holdMode,
          statuses: filterMap[this.state.filterBy],
        })
          .then((o) => {
            const { orders } = o.data;
            const allOrders = this.state.orders.concat(orders);
            this.setState({
              orders: allOrders,
              isLoading: false,
              page: this.state.page + 1,
              pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
              // Just in case we're in the middle of a prefetch.
              prefetchedOrders: null,
            });
          })
          .catch(e => console.error(e.message));
      },
    );
  };

преобразовать в функцию на основе

  const getNextPage = () => {
    // This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
    goToTop();

    if (pagesSeen.includes(page + 1)) {
      return setPage(page + 1);
    }

    if (prefetchedOrders) {
      const allOrders = orders.concat(prefetchedOrders);
      setOrders(allOrders);
      setPage(page + 1);
      setPagesSeen([...pagesSeen, page + 1]);
      setPrefetchedOrders(null);
      return;
    }

    setIsLoading(true);

    getOrders({
      page: page + 1,
      query: localQuery,
      held: localHoldMode,
      statuses: filterMap[filterBy],
    })
      .then((o) => {
        const { orders: fetchedOrders } = o.data;
        const allOrders = orders.concat(fetchedOrders);

        setOrders(allOrders);
        setPage(page + 1);
        setPagesSeen([...pagesSeen, page + 1]);
        setPrefetchedOrders(null);
        setIsLoading(false);
      })
      .catch(e => console.error(e.message));
  };

Выше мы хотим запускать каждый вызов setWh независимо последовательно. Означает ли это, что нам нужно установить множество различных хуков useEffect для репликации этого поведения?

Ответы

Ответ 1

useState не обеспечивает обратный вызов после обновления состояния, как setState в компонентах класса React. Чтобы повторить то же поведение, вы можете использовать похожий шаблон, такой как метод жизненного цикла componentDidUpdate в компонентах класса React с помощью useEffect с использованием хуков.

useEffect принимают второй параметр в виде массива значений, которые React должен отслеживать на предмет изменений после завершения цикла рендеринга.

const [loading, setLoading] = useState(false);

...

useEffect(() => {
    doSomething(); // This is be executed when 'loading' state changes
}, [loading])
setLoading(true);

РЕДАКТИРОВАТЬ

В отличие от setState, setState обновления для useState не имеет обратного вызова, но вы всегда можете использовать useEffect для воспроизведения описанного выше поведения. Однако вам нужно определить изменение загрузки

Функциональный подход к вашему коду будет выглядеть

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

const prevLoading = usePrevious(isLoading);

useEffect(() => {
   if (!prevLoading && isLoading) {
       getOrders({
          page: page + 1,
          query: localQuery,
          held: localHoldMode,
          statuses: filterMap[filterBy],
      })
      .then((o) => {
        const { orders: fetchedOrders } = o.data;
        const allOrders = orders.concat(fetchedOrders);

        setOrders(allOrders);
        setPage(page + 1);
        setPagesSeen([...pagesSeen, page + 1]);
        setPrefetchedOrders(null);
        setIsLoading(false);
      })
      .catch(e => console.error(e.message));
   }
}, [isLoading, preFetchedOrders, orders, page, pagesSeen]);

const getNextPage = () => {
    // This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
    goToTop();

    if (pagesSeen.includes(page + 1)) {
      return setPage(page + 1);
    }

    if (prefetchedOrders) {
      const allOrders = orders.concat(prefetchedOrders);
      setOrders(allOrders);
      setPage(page + 1);
      setPagesSeen([...pagesSeen, page + 1]);
      setPrefetchedOrders(null);
      return;
    }

    setIsLoading(true);
  };

Ответ 2

Подождите, пока ваш компонент не выполнит рендеринг.

const [loading, setLoading] = useState(false);

useEffect(() => {
    if (loading) {
        doSomething();
    }
}, [loading]);

setLoading(true);

Вы можете улучшить ясность с чем-то вроде:

function doSomething() {
  // your side effects
  // return () => {  }
}

function useEffectIf(condition, fn) {
  useEffect(() => condition && fn(), [condition])
}

function App() {
  const [loading, setLoading] = useState(false);
  useEffectIf(loading, doSomething)

  return (
    <>
      <div>{loading}</div>
      <button onClick={() => setLoading(true)}>Click Me</button>
    </>
  );
}

Ответ 3

Вам нужно вызвать ' setState ' (setLoading) немного по-другому...

function App() {
  const [loading, setLoading] = useState(false);
  return (
    <div>
      {JSON.stringify(loading)} // to see our state
      <button onClick={() => setLoading(true)}>Click Me</button>
    </div>
  );
}

Демо: https://codesandbox.io/s/xvmp92zqjz