Реагировать на useEffect, выполняя последнюю функцию

Мне нужно иметь 2 разные функции, которые обновляют 2 разных компонента только один раз в начале. Поэтому я использую useEffect. Код выглядит следующим образом

const loadCategories = () => {
    getCategories().then((c) => setValues({ ...values, categories: c.data }));
}

const loadStores = () => {
    getStores().then((c) => setValues({ ...values, stores: c.data }));
}

useEffect(() => {
  loadStores();
  loadCategories();
}, []);

Обе функции устанавливают значения выпадающих элементов.

Проблема в том, что обе функции выполняются, но в пользовательском интерфейсе отображается только логика loadCategories() функций. Как заставить логику обеих функций отражаться в пользовательском интерфейсе?

🤔 А знаете ли вы, что...
JavaScript может выполняться как на стороне клиента (в браузере), так и на стороне сервера (с использованием Node.js).


419
4

Ответы:

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

useEffect(() => {
    Promise.all([getCategories(), getStores()]).then(([categories, stores]) => {
      setValues({categories, stores})
    });
}, []);

Проблема с тем, что у вас было раньше (как вы поняли), заключается в том, что values всегда является значением в точке, в которой useState запускался на этом рендере.

Если вы действительно хотите делать обновления отдельно, вы можете заглянуть в useReducer: https://reactjs.org/docs/hooks-reference.html#usereducer


Обещание и useEffect могут быть сложными, поскольку компонент может отключиться до того, как обещание будет полностью заполнено.

Вот решение, которое работает довольно хорошо:

useEffect(() => {
  let isRunning = true;
  Promise.all([ 
    getCategories(),
    getStores()
  ]).then(([stores, categories]) => {
    // Stop if component was unmounted:
    if (!isRunning) { return }
    // Do anything you like with your lazy load values:
    console.info(stores, categories)
  });
  return () => {
    isRunning = false;
  }
}, []);

Вы лжете React о зависимостях. Обе ваши функции зависят от переменной состояния values. Вот почему это также не работает: когда хуки запускаются, values закрывается, затем, когда setValues запускается, values изменяется (устанавливается на новый объект), однако внутри замыкания он все еще ссылается на старые значения.

Вы можете легко решить эту проблему, передав обратный вызов setValues, таким образом, у вас не будет зависимости от внешнего мира, а обновление значений будет атомарным:

 setValues(values => ({ ...values, stores: c.data }));

Решено

сначала лучше добавить эти функции в useEffect или обернуть их в хук useCallback. во-вторых, обе или ваша функция являются обещаниями, поэтому каждая из них может не разрешаться одновременно, и когда вы пытаетесь обновить значения состояния, оно сохранит начальное значение, поэтому ваша первая функция не отражается в пользовательском интерфейсе, вместо этого используйте setState callback, чтобы получить предыдущее состояние следующим образом:

useEffect(() => {
const loadCategories = () => {
    getCategories().then((c) => setValues(prevState=>({ ...prevState, categories: c.data })));
}

const loadStores = () => {
    getStores().then((c) => setValues(prevState=>({ ...prevState, stores: c.data })));
}
  loadStores();
  loadCategories();
}, []);