React Hooks использует бесконечный цикл зависимостей callback

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

Однако мне нужно добавить isFetching к зависимости useCallback, чтобы удалить предупреждение, и если я это сделаю, запускается бесконечный цикл выборки.

Вот мой код:

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

export const MyComponent = () => {
  const isMounted = useRef(true);
  const [isFetching, setIsFetching] = useState(false);
  
  const [data, setData] = useState(null);
  
  // Can also be called from the button click
  const getMyData = useCallback(() => {
    if (isFetching) return;
    setIsFetching(true);
    fetch('get/my/data')
      .then((res) => {
        if (isMounted.current) {
          setData(res.data);
        }
      })
      .catch((err) => {
        if (isMounted.current) {
          setData("Error fetching data");
        }
      })
      .finally(() => {
        if (isMounted.current) {
          setIsFetching(false);
        }
      });
  }, []); // isFetching dependency warning as is, if added then infinite loop

  useEffect(() => {
    isMounted.current = true;
    getMyData();
    return () => {
      isMounted.current = false;
    };
  }, [getMyData]);

  return (
    <div>
        <button onClick={getMyData}>Update data</button>
        <p>{data}</p>
    </div>
  );
};

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

Вот несколько примеров: example-1, пример-2


person Anis Benna    schedule 20.02.2021    source источник


Ответы (1)


Преобразуйте isFetching в ссылку, чтобы его значение не зависело от функции:

const { useCallback, useEffect, useRef, useState } = React;

const MyComponent = () => {
  const isMounted = useRef(true);
  const isFetching = useRef(false);
  
  const [data, setData] = useState([]);
  
  // Can also be called from the button click
  const getMyData = useCallback(() => {
    console.log('call');
    if (isFetching.current) return;
    isFetching.current = true;
    fetch('https://cat-fact.herokuapp.com/facts')
      .then(res => res.json())
      .then(res => {
        if (isMounted.current) {
          setData(res);
        }
      })
      .catch((err) => {
        if (isMounted.current) {
          setData("Error fetching data");
        }
      })
      .finally(() => {
        if (isMounted.current) {
          isFetching.current = false;
        }
      });
  }, []); // isFetching dependency warning as is, if added then infinite loop

  useEffect(() => {
    isMounted.current = true;
    getMyData();
    return () => {
      isMounted.current = false;
    };
  }, [getMyData]);

  return (
    <div>
      <button onClick={getMyData}>Update data</button>
      <ul>
      {
        data.map(({ _id, text }) => (
          <li key={_id}>{text}</li>
        ))
      }
      </ul>
    </div>
  );
};

ReactDOM.render(
  <MyComponent />,
  root
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>

person Ori Drori    schedule 20.02.2021