Пользовательский хук с useEffect (при монтировании) выполняется более одного раза

У меня есть этот хук, который вызывает действие redux при монтировании. Так что я предполагаю, что это нужно выполнить один раз. Но это не так.

useCategories.js

const useCategories = () => {
  /*
    Hook to call redux methods that will fetch the categories 
    of the exercises. Store it in redux store.

    RETURN: loading, error, data
  */
  const mainCategories = useSelector(state => state.categories.specialities);
  const loading = useSelector(state => state.categories.loading);
  const error = useSelector(state => state.categories.error);

  const dispatch = useDispatch();
  const onInitExercises = useCallback(
    () => dispatch(actions.fetchCategories()),
    [dispatch]
  );

  useEffect(() => {
    console.log("[useCategories] --> UseEffect");
    onInitExercises();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [onInitExercises]);

  const data = { specialities: [] };
  if (mainCategories) {
    console.log("Inside If mainCategories");
    for (let key in mainCategories) {
      data["specialities"].push(mainCategories[key]);
     }
  }

  console.log("[useCategories] --> Before Return");
  return [loading, error, data];
};

export default useCategories;

И теперь я использую его в другом компоненте

Боковая панель

import React, { useState, useEffect, useRef } from "react";
import useCategories from "../hookFetchCategories/useCategories";

const SideBarFilters = props => {

  const [orderForm, setOrderForm] = useState({})
  // Hook to fetch categories from DB
  const [loading, error, categoriesData] = useCategories();

  // Function to update form after categories are fetched
  const updateSpecialitiesData = useRef((formElementKey, data) => {
    console.log("Inside Update");
    // code to update orderForm with data fetched in useCategories()
    setOrderForm(orderFormUpdated);
  });

  useEffect(() => {
    if (!loading && categoriesData && categoriesData["specialities"].length) {
      console.log("Inside IF");
      updateSpecialitiesData("specialities", categoriesData["specialities"]);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [loading, updateSpecialitiesData]);

  // --- Rest of the component
}

Когда я запускаю приложение, в консоли DEV у меня есть журналы консоли

[useCategories] --> Before Return
useCategories.js:23 [useCategories] --> UseEffect
useCategories.js:53 [useCategories] --> Before Return
useCategories.js:53 [useCategories] --> Before Return
useCategories.js:42 Inside if mainCategories
useCategories.js:53 [useCategories] --> Before Return
SideBarFilters.js:61 Inside IF
SideBarFilters.js:36 Inside Update
useCategories.js:42 Inside if mainCategories
useCategories.js:53 [useCategories] --> Before Return

1) Я не понимаю, почему перехватчик useCategories не выполняется только один раз?

Это то, что я понимаю в отношении того, как должен вести себя useEffect в useCategories.

2) Если я добавлю в SideBar.js, в ловушку useEffect, зависимость «CategoriesData», начнется бесконечный цикл.


person Gonzalo    schedule 22.01.2020    source источник
comment
нам нужно увидеть ваш метод рендеринга   -  person Chris Hawkes    schedule 23.01.2020


Ответы (1)


Я не понимаю, почему хук useCategories не выполняется только один раз?

Все хуки выполняются каждый раз при рендеринге компонента, они просто делают разные вещи при выполнении. Ваш useCategories пользовательский хук - это просто функция, которая вызывает другие хуки, предоставляемые React.

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

[useCategories] --> Before Return

Поскольку вы вызываете функцию useCategories при рендеринге, она регистрирует это сообщение.

Однако вы заметите, что это сообщение:

[useCategories] --> UseEffect

Появляется только один раз. Это связано с тем, что хук useEffect (как вы его настроили) будет запускаться только один раз, сразу после первого рендеринга.

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


Если я добавлю в SideBar.js в хук useEffect зависимость categoriesData, он запустит бесконечный цикл.

Зависимости сравниваются по идентичности, а не по ценности. Думайте ===, а не ==. Это означает, что в случае объектов сравнение спрашивает: «Это тот же самый объект? Если нет, запустите эффект еще раз».

Если вы посмотрите на свой собственный хук, вы увидите эту строку:

const data = { specialities: [] };

Это создает новый объект каждый раз, когда запускается ловушка (то есть при каждой визуализации). Несмотря на то, что у него могут быть одинаковые значения, при каждом рендеринге это будет другой объект. Это означает, что он будет аннулировать массив зависимостей при каждом рендеринге.

Есть много способов исправить это, но один способ - useMemo, который создаст новый объект только тогда, когда изменятся значения, которые изменили бы его значение.

const data = useMemo(() => {
   const myFancyData = {} // logic to create your data here
   return myFancyData
}, [dependenciesFor, above, logic, here])

Или, если подумать, зачем вы вообще перестраиваете эти данные? data - это просто созданный вручную клон mainCategories, так почему бы просто не:

return [loading, error, mainCategories];

Redux должен возвращать тот же объект из useSelector, если данные не изменились.

person Alex Wayne    schedule 22.01.2020
comment
Большой. Спасибо. Теперь, как лучше всего (если он есть) сделать это. Поскольку useCategories будет выполняться при каждом рендеринге, if (mainCategories) будет истинным при каждом рендеринге (после того, как они будут извлечены). Я бы хотел, чтобы это выполнялось только один раз после их получения. С использованием useMemo это будет решено. Но есть ли лучший подход? Спасибо, Алекс! @ Alex-Wayne - person Gonzalo; 23.01.2020
comment
Смотрите мою правку в конце. Я думаю, ты здесь стараешься больше, чем нужно. - person Alex Wayne; 23.01.2020
comment
Сейчас я привожу только state.categories.specialities. Но мне нужно будет принести больше данных и провести с ними некоторую обработку. Некоторая фильтрация. Вот почему у меня есть этот дополнительный код - person Gonzalo; 23.01.2020