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

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

...

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

Состояние настройки по-прежнему асинхронное, поэтому как лучше всего дождаться завершения этого вызова 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));
  };

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

Ответы (4)

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

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

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

...

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

ИЗМЕНИТЬ

В отличие от 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);
  };

У меня есть предложение по этому поводу.

Вы могли бы использовать React Ref для хранения состояния переменной состояния. Затем обновите переменную состояния с помощью response ref. Это отрендерит обновление страницы, а затем будет использовать React Ref в асинхронной функции.

const stateRef = React.useRef().current
const [state,setState] = useState(stateRef);

async function some() {
  stateRef = { some: 'value' }
  setState(stateRef) // Triggers re-render
  
  await some2();
}

async function some2() {
  await someHTTPFunctionCall(stateRef.some)
  stateRef = null;
  setState(stateRef) // Triggers re-render
}

Дождитесь повторного рендеринга вашего компонента.

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 (
    <>
      
{loading}
); }

Создан пользовательский хук useState, который работает аналогично обычному хуку useState, за исключением того, что функция обновления состояния для этого пользовательского хука принимает обратный вызов, который будет выполнен после обновления состояния и компонент повторно отрисован.

Машинописное решение

import { useEffect, useRef, useState } from 'react';

type OnUpdateCallback = (s: T) => void;
type SetStateUpdaterCallback = (s: T) => T;
type SetStateAction = (newState: T | SetStateUpdaterCallback, callback?: OnUpdateCallback) => void;

export function useCustomState(init: T): [T, SetStateAction];
export function useCustomState(init?: T): [T | undefined, SetStateAction];
export function useCustomState(init: T): [T, SetStateAction] {
    const [state, setState] = useState(init);
    const cbRef = useRef>();

    const setCustomState: SetStateAction = (newState, callback?): void => {
        cbRef.current = callback;
        setState(newState);
    };

    useEffect(() => {
        if (cbRef.current) {
            cbRef.current(state);
        }
        cbRef.current = undefined;
    }, [state]);

    return [state, setCustomState];
}

Решение Javascript

import { useEffect, useRef, useState } from 'react';

export function useCustomState(init) {
    const [state, setState] = useState(init);
    const cbRef = useRef();

    const setCustomState = (newState, callback) => {
        cbRef.current = callback;
        setState(newState);
    };

    useEffect(() => {
        if (cbRef.current) {
            cbRef.current(state);
        }
        cbRef.current = undefined;
    }, [state]);

    return [state, setCustomState];
}

Использование

const [состояние, setState] = useCustomState (myInitialValue);
...
setState (myNewValueOrStateUpdaterCallback, () => {
   // Функция вызывается после обновления состояния и повторной визуализации компонента
})

2022 WebDevInsider