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

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

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

...

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

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

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

пример

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

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, чтобы воспроизвести это поведение?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
114
0
124 285
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

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>
    </>
  );
}

Хм, так мне нужно добавить useEffect() для отслеживания каждого состояния и реагирования на него?

Colin Ricardo 22.12.2018 21:10

@Colin вы можете думать об useEffect как о componentDidMount или componentDidUpdate в зависимости от последнего аргумента, переданного обратному вызову в примере выше [загрузка]

SakoBu 22.12.2018 21:13

Честно говоря, я не думаю, что это хороший ответ; Можете ли вы лучше объяснить, что на самом деле делает doSomething и каков ваш вариант использования?

Federkun 22.12.2018 21:16

Я думаю, что на данный момент это имеет смысл. Это надуманный пример, но doSomething() извлекается из API и должен знать текущее состояние действительный, если это имеет смысл.

Colin Ricardo 22.12.2018 21:18

@Colin Хорошее объяснение с примерами хуков API egghead.io/lessons/react-use-the-usestate-react-hook

SakoBu 22.12.2018 21:21

Тогда да, useEffect - это то, что вам нужно

Federkun 22.12.2018 21:35

Почему бы не вызвать setLoading в эффекте, если doSomething правильно выбирает из Api, может быть так: useEffect(() => { setLoading(true); doSomething().finally(() => setLoading(false))}, []). Обратите внимание, что [] используется для выполнения эффектов только таких, как componentDidMount.

Olivier Boissé 23.12.2018 00:27
Ответ принят как подходящий

Сеттер 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, программа обновления для перехвата 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);
  };

Итак, в случае наличия нескольких setState, которых мы хотим дождаться, нам нужно несколько хуков useEffect, верно?

Colin Ricardo 20.02.2019 18:34

@Colin, если вы хотите выполнять разные действия при изменении разных состояний, тогда да, но если вы хотите сделать то же самое при любом из этих изменений нескольких состояний, вы можете передать несколько аргументов, таких как [isLoading, isUpdated, showError]

Shubham Khatri 20.02.2019 18:36

да, так что в приведенном выше случае у вас будет [isLoading, orders]

Shubham Khatri 20.02.2019 18:38

но чтобы выполнять разные действия, нам нужно несколько хуков useEffect, верно? Кроме того, как мы можем таким образом выполнять последовательные действия? Разве изменение не приведет к одновременному срабатыванию обоих хуков?

Colin Ricardo 20.02.2019 18:46

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

Shubham Khatri 20.02.2019 18:48

Обновил ответ

Shubham Khatri 20.02.2019 19:11

Этот крючок для usePrevious - приятный штрих, я такого раньше не видел - спасибо, что поделились!

ohsully 02.08.2019 23:41

Кстати, вы можете использовать несколько объявлений useEffect, каждое из которых связано с интересующим вас свойством.

ACV 14.10.2020 13:51

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

Вы могли бы использовать 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
}

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

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

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

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

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

    const setCustomState: SetStateAction<T> = (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 [state, setState] = useCustomState(myInitialValue);
...
setState(myNewValueOrStateUpdaterCallback, () => {
   // Function called after state update and component rerender
})

Другие вопросы по теме