Home > Software design >  useEffect dependency causes infinite loop
useEffect dependency causes infinite loop

Time:04-06

I created a custom hook which I use in App.js

The custom hook (relevant function is fetchTasks):

export default function useFetch() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);
  const [tasks, setTasks] = useState([]);

  const fetchTasks = async (url) => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(url);

      if (!response.ok) {
        throw new Error("falied!");
      }
      const data = await response.json();
      const loadedTasks = [];
      for (const taskKey in data) {
        loadedTasks.push({ id: taskKey, text: data[taskKey].text });
      }

      setTasks(loadedTasks);
    } catch (err) {
      console.log(err.message);
    }
    setLoading(false);
  };

  return {
    loading,
    setLoading,
    error,
    setError,
    fetchTasks,
    tasks,
  };
}

Then in my App.js:

function App() {
  const { loading, setLoading, error, setError, fetchTasks, tasks } =
    useFetch();

  useEffect(() => {
    console.log("fetching");
    fetchTasks(
      "https://.....firebaseio.com/tasks.json"
    );
  }, []);

My IDE suggests adding the fetchTasks function as a dependency to useEffect. But once I add it, an infinite loop is created. If I omit it from the dependencies as shown in my code, it will work as expected, but I know this is a bad practice. What should I do then?

CodePudding user response:

Because that every time you call useFetch(). fetchTasks function will be re-created. That cause the reference to change at every render then useEffect() will detected that dependency fetchTasks is re-created and execute it again, and make the infinite loop. So you can leverage useCallback() to memoize your fetchTasks() function so the reference will remains unchanged.

import { useCallback } from 'react'

export default function useFetch() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);
  const [tasks, setTasks] = useState([]);

  const fetchTasks = useCallback(
    async (url) => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(url);

      if (!response.ok) {
        throw new Error("falied!");
      }
      const data = await response.json();
      const loadedTasks = [];
      for (const taskKey in data) {
        loadedTasks.push({ id: taskKey, text: data[taskKey].text });
      }

      setTasks(loadedTasks);
    } catch (err) {
      console.log(err.message);
    }
    setLoading(false);
  };,[])

  return {
    loading,
    setLoading,
    error,
    setError,
    fetchTasks,
    tasks,
  };
}
function App() {
  const { loading, setLoading, error, setError, fetchTasks, tasks } =
    useFetch();

  useEffect(() => {
    console.log("fetching");
    fetchTasks(
      "https://.....firebaseio.com/tasks.json"
    );
  }, [fetchTasks]);

CodePudding user response:

instead of return fetchTasks function return this useCallback fetchTasksCallback function from useFetch hook which created only one instance of fetchTasksCallback.

const fetchTasksCallback = useCallback(
  (url) => {
    fetchTasks(url);
  },
  [],
);

function App() {
  const { loading, setLoading, error, setError, fetchTasksCallback, tasks } =
    useFetch();

  useEffect(() => {
    console.log("fetching");
    fetchTasksCallback(
      "https://.....firebaseio.com/tasks.json"
    );
  }, [fetchTasksCallback]);

the problem is this fetchTasks every time create a new instance that way dependency list feels that there is a change and repeats the useEffect code block which causes the infinite loop problem

  • Related