Как исправить предупреждение об отсутствующей зависимости при использовании useEffect React Hook?

182

В React 16.8.6 (это было хорошо в предыдущей версии 16.8.3) я получаю эту ошибку, когда пытаюсь предотвратить бесконечный цикл при запросе выборки.

./src/components/BusinessesList.js
Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array  react-hooks/exhaustive-deps

Я не смог найти решение, которое останавливает бесконечный цикл. Я хочу держаться подальше от использования useReducer(). Я нашел это обсуждение https://github.com/facebook/react/issues/14920, где возможное решение, You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing.я не уверен в том, что я делаю, поэтому я еще не пытался реализовать его.

У меня есть эта текущая настройка React ловушка useEffect работает постоянно навсегда / бесконечный цикл, и единственный комментарий о useCallback()котором я не знаком.

Как я в настоящее время использую useEffect()(который я хочу запустить только один раз в начале componentDidMount())

useEffect(() => {
    fetchBusinesses();
  }, []);
const fetchBusinesses = () => {
    return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };
русс
источник

Ответы:

194

Если вы не используете метод fetchBususiness где-либо кроме эффекта, вы можете просто переместить его в эффект и избежать предупреждения

useEffect(() => {
    const fetchBusinesses = () => {
       return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };
  fetchBusinesses();
}, []);

Однако, если вы используете fetchBususiness за пределами рендера, вы должны отметить две вещи

  1. Есть ли какие - либо проблемы с вами не проходит fetchBusinessesкак метод , когда он используется в процессе монтажа с его закрытием ограждающей?
  2. Зависит ли ваш метод от некоторых переменных, которые он получает от замыкающего вложения? Это не тот случай для вас.
  3. При каждом рендеринге fetchBususiness будет воссоздан, и, следовательно, передача его в useEffect вызовет проблемы. Итак, сначала вы должны запомнить fetchBusprises, если вы должны были передать его в массив зависимостей.

Подводя итог, я бы сказал, что если вы используете fetchBusinessesвне, useEffectвы можете отключить правило, используя в // eslint-disable-next-line react-hooks/exhaustive-depsпротивном случае вы можете переместить метод внутри useEffect

Чтобы отключить правило, вы должны написать его так

useEffect(() => {
   // other code
   ...

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 
Шубхам Хатри
источник
14
Я использовал решение, которое вы обрисовали в общих чертах. Другое решение, которое я использовал еще где-то из-за другой установки useCallback(). Так, например: const fetchBusinesses= useCallback(() => { ... }, [...]) и useEffect()будет выглядеть так:useEffect(() => { fetchBusinesses(); }, [fetchBusinesses]);
russ
1
@ russ, вы правы, вам нужно будет запомнить fetchBusiness, используя useCallback, если вы хотите передать его в массив зависимостей
Shubham Khatri
Было бы неплохо, если бы вы показали, куда поместить оператор eslint-disable. Я думал, что это будет выше useEffect
user210757
1
использовать // eslint-disable-next-line react-hooks/exhaustive-depsдля объяснения линтеру, что ваш код правильный, как хак. Я ожидаю, что они найдут другое решение, чтобы сделать линтер достаточно умным, чтобы обнаружить, когда аргумент не является обязательным
Оливье Буасе,
1
@TapasAdhikary, да, вы можете использовать асинхронную функцию useEffect, вам просто нужно написать ее по-другому. Пожалуйста, проверьте stackoverflow.com/questions/53332321/…
Шубхам Кхатри
77
./src/components/BusinessesList.js
Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array  react-hooks/exhaustive-deps

Это не ошибка JS / React, а предупреждение eslint (eslint-plugin-response-hooks).

Он говорит вам, что ловушка зависит от функции fetchBusinesses, поэтому вы должны передать ее как зависимость.

useEffect(() => {
  fetchBusinesses();
}, [fetchBusinesses]);

Это может привести к вызову функции при каждом рендеринге, если функция объявлена ​​в компоненте, например:

const Component = () => {
  /*...*/

  //new function declaration every render
  const fetchBusinesses = () => {
    fetch('/api/businesses/')
      .then(...)
  }

  useEffect(() => {
    fetchBusinesses();
  }, [fetchBusinesses]);

  /*...*/
}

потому что каждый раз функция повторно объявлена ​​с новой ссылкой

Правильный способ сделать это:

const Component = () => {
  /*...*/

  // keep function reference
  const fetchBusinesses = useCallback(() => {
    fetch('/api/businesses/')
      .then(...)
  }, [/* additional dependencies */]) 

  useEffect(() => {
    fetchBusinesses();
  }, [fetchBusinesses]);

  /*...*/
}

или просто определяя функцию в useEffect

Подробнее: https://github.com/facebook/react/issues/14920

фард
источник
14
Это приводит к новой ошибкеLine 20: The 'fetchBusinesses' function makes the dependencies of useEffect Hook (at line 51) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'fetchBusinesses' definition into its own useCallback() Hook
russ
1
решение хорошо, и если в функции вы изменяете другое состояние, вам нужно добавить зависимости, чтобы избежать другого неожиданного поведения
cesarlarsson
60

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

useEffect(fetchBusinesses, [])

Он сработает только один раз, поэтому убедитесь, что все зависимости функции установлены правильно (так же, как и при использовании componentDidMount/componentWillMount...)


Изменить 21.02.2020

Просто для полноты:

1. Используйте функцию в качестве useEffectобратного вызова (как указано выше)

useEffect(fetchBusinesses, [])

2. Объявить функцию внутри useEffect()

useEffect(() => {
  function fetchBusinesses() {
    ...
  }
  fetchBusinesses()
}, [])

3. Поминать с useCallback()

В этом случае, если у вас есть зависимости в вашей функции, вам придется включить их в useCallbackмассив зависимостей, и это вызовет useEffectснова, если параметры функции изменятся. Кроме того, это много шаблонного ... Так что просто передайте функцию напрямую, useEffectкак в 1. useEffect(fetchBusinesses, []).

const fetchBusinesses = useCallback(() => {
  ...
}, [])
useEffect(() => {
  fetchBusinesses()
}, [fetchBusinesses])

4. Отключить предупреждение Эслинта

useEffect(() => {
  fetchBusinesses()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
jpenna
источник
2
Я люблю тебя ... Этот ответ настолько полный!
Nick09
8

Решение также дается реакцией, они советуют вам использовать, useCallbackчто вернет запоминающуюся версию вашей функции:

Функция «fetchBususiness» изменяет зависимости useEffect Hook (в строке NN) при каждом рендеринге. Чтобы исправить это, оберните определение 'fetchBususiness' в его собственное useCallback () Hook реагировать-ловит / исчерпывающе-deps

useCallbackОн прост в использовании, поскольку имеет ту же сигнатуру, что useEffectи различие в том, что useCallback возвращает функцию. Это будет выглядеть так:

 const fetchBusinesses = useCallback( () => {
        return fetch("theURL", {method: "GET"}
    )
    .then(() => { /* some stuff */ })
    .catch(() => { /* some error handling */ })
  }, [/* deps */])
  // We have a first effect thant uses fetchBusinesses
  useEffect(() => {
    // do things and then fetchBusinesses
    fetchBusinesses(); 
  }, [fetchBusinesses]);
   // We can have many effect thant uses fetchBusinesses
  useEffect(() => {
    // do other things and then fetchBusinesses
    fetchBusinesses();
  }, [fetchBusinesses]);
Стефан Л
источник
2

Эти предупреждения очень полезны для поиска компонентов, которые не обновляются последовательно: https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of- зависимости .

Однако, если вы хотите удалить предупреждения во всем проекте, вы можете добавить это в конфигурацию eslint:

  {
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/exhaustive-deps": 0
    }
  }
Джордан Дэниелс
источник
1

Эта статья является хорошим учебником по извлечению данных с помощью хуков: https://www.robinwieruch.de/react-hooks-fetch-data/

По сути, включите определение функции извлечения внутри useEffect:

useEffect(() => {
  const fetchBusinesses = () => {
    return fetch("theUrl"...
      // ...your fetch implementation
    );
  }

  fetchBusinesses();
}, []);
helloitsjoe
источник
1

Вы можете удалить массив 2-го типа аргумента, []но fetchBusinesses()он также будет вызываться при каждом обновлении. Вы можете добавить IFзаявление в fetchBusinesses()реализацию, если хотите.

React.useEffect(() => {
  fetchBusinesses();
});

Другой - реализовать fetchBusinesses()функцию за пределами вашего компонента. Только не забудьте передать любые аргументы зависимости на ваш fetchBusinesses(dependency)вызов, если таковые имеются.

function fetchBusinesses (fetch) {
  return fetch("theURL", { method: "GET" })
    .then(res => normalizeResponseErrors(res))
    .then(res => res.json())
    .then(rcvdBusinesses => {
      // some stuff
    })
    .catch(err => {
      // some error handling
    });
}

function YourComponent (props) {
  const { fetch } = props;

  React.useEffect(() => {
    fetchBusinesses(fetch);
  }, [fetch]);

  // ...
}
5ervant
источник
0

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

Если вы не хотите помещать fetchBusinessesв зависимости хука, вы можете просто передать его в качестве аргумента обратному вызову хука и установить fetchBusinessesдля него значение main по умолчанию, например:

useEffect((fetchBusinesses = fetchBusinesses) => {
   fetchBusinesses();
}, []);

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

Также, как писал Шубнам, вы можете добавить код ниже, чтобы ESLint игнорировал проверку вашего хука.

// eslint-disable-next-line react-hooks/exhaustive-deps
Бехнам Азими
источник
0

Я хочу запустить [ fetchBusinesses] только один раз в началеcomponentDidMount()

Вы можете fetchBusinessesполностью вытащить свой компонент:

const fetchBusinesses = () => { // or pass some additional input from component as args
  return fetch("theURL", { method: "GET" }).then(n => process(n));
};

const Comp = () => {
  React.useEffect(() => {
    fetchBusinesses().then(someVal => {
      // ... do something with someVal
    });
  }, []); // eslint warning solved!
  return <div>{state}</div>;
};

Это не только предоставит простое решение и решит проблему исчерпывающего предупреждения. fetchBusinessтеперь может быть проверен лучше и облегчаетComp , поскольку он находится в области видимости модуля вне дерева React.

Перемещение fetchBusinessesза пределы хорошо работает здесь, так как мы будем в состоянии только прочитать начальные реквизиты и состояние из компонента в любом случае из-за устаревшей области закрытия ( []dep in useEffect).

Как опустить зависимости функций

  • Переместить функцию внутрь эффекта
  • Переместить функцию за пределы компонента - (мы используем это)
  • Вызов функции во время рендеринга и пусть useEffectзависит от этого значения (чисто вычислительная функция)
  • Добавьте функцию, чтобы произвести эффект и оберните ее useCallback как последнее средство

По поводу других решений:

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

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

ford04
источник
0
const [mount, setMount] = useState(false)
const fetchBusinesses = () => { 
   //function defination
}
useEffect(() => {
   if(!mount) {
      setMount(true);
      fetchBusinesses();
   }
},[fetchBusinesses]);

Это решение довольно простое, и вам не нужно переопределять предупреждения es-lint. Просто установите флажок, чтобы проверить, смонтирован ли компонент или нет.

Ясин
источник
0

ты попробуй так

const fetchBusinesses = () => {
    return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };

и

useEffect(() => {
    fetchBusinesses();
  });

это работа для вас. Но я предлагаю попробовать этот способ и работать на вас. Это лучше, чем раньше. Я использую этот способ:

useEffect(() => {
        const fetchBusinesses = () => {
    return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };
        fetchBusinesses();
      }, []);

если вы получаете данные на основе определенного идентификатора, а затем добавьте в функцию обратного вызова useEffect, [id]то вы не сможете отобразить предупреждение React Hook useEffect has a missing dependency: 'any thing'. Either include it or remove the dependency array

Kashif
источник
-4

просто отключите eslint для следующей строки;

useEffect(() => {
   fetchBusinesses();
// eslint-disable-next-line
}, []);

таким образом, вы используете его так же, как компонент монтировал (вызывается один раз)

обновленный

или

const fetchBusinesses = useCallback(() => {
 // your logic in here
 }, [someDeps])

useEffect(() => {
   fetchBusinesses();
// no need to skip eslint warning
}, [fetchBusinesses]); 

fetchBususiness будет вызываться каждый раз, когда некоторые изменения будут меняться

user3550446
источник
вместо того, чтобы отключить, просто сделав это: [fetchBusinesses]автоматически удалит предупреждение, и это решило проблему для меня.
rotimi-best
7
@RotimiBest - выполнение этого вызывает бесконечный повторный рендер, как описано в вопросе
user210757
Я действительно делал это таким образом в одном из моих проектов некоторое время назад, и это не давало бесконечного цикла. Я проверю еще раз, хотя.
rotimi-best
@ user210757 Подождите, но почему это вызовет бесконечный цикл, не то, чтобы вы устанавливали состояние после получения данных с сервера. Если вы обновляли состояние, просто напишите условие if перед вызовом функции, useEffectкоторая проверяет, является ли состояние пустым.
Ротими-бест
@ rotimi-лучше всего было так с тех пор, как я прокомментировал, но я бы сказал, что функция создается заново каждый раз, поэтому никогда не повторяется, поэтому всегда будет повторяться, если вы не перейдете в тело useEffect или useCallback
user210757