2

I'm currently fetching my data once when the component mounts, then whenever the user clicks a button. I want however to stop the button from fetching if a request is in progress, that's why I'm updating the isFetching state.

However, I need to add the isFetching to the useCallback dependency to remove the warning and if I do, an infinite fetch loop is triggered.

Here's my code:

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

I understand that there are multiple questions like this one, but I couldn't remove the warning or infinite loop while still checking if the component is mounted.

Here are some examples: example-1, example-2

1 Answer 1

3

Convert isFetching to a ref, so it's value won't be a dependency of the function:

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>

Sign up to request clarification or add additional context in comments.

2 Comments

How to use this ref to toggle loading indicator?
Changing the ref won't cause a re-render, and the view won't change. You'll need to add another state (loading), and toggle it while you toggle the fetching ref.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.