infinite loop in useeffect

  • Last Update :
  • Techknowledgy :

1 An empty array at the end of a useEffect is a purposeful implementation by the developers to stop infinite loops in situations where you may, for instance, need to setState inside of a useEffect. This would otherwise lead to useEffect -> state update -> useEffect -> infinite loop. – to240 Jul 24, 2020 at 10:09 Passing an empty array as the second argument to useEffect makes it only run on mount and unmount, thus stopping any infinite loops. This is the real answer, for those of us in a situation where we really do need to modify state and so can't list it as a dependency or we get an infinite loop. – Malvineous 11 hours ago

This is our Splunktool team suggestion ✌, we tried and its working fine
// Add [] at enduseEffect(() => {
   myFn();
}, []); // <--
2._
useEffect(() => {
   setIngredients({});
}, []);

Had the same problem. I don't know why they not mention this in docs. Just want to add a little to Tobias Haugen answer.

To run in every component/parent rerender you need to use:

  useEffect(() => {

     // don't know where it can be used :/
  })

To run anything one time on component mount and on data/data2 change:

  const [data, setData] = useState(false)
  const [data2, setData2] = useState('default value for first render')
  useEffect(() => {

     // if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed
     // if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone).
     // if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this)
  }, [data, data2])

How i use it most of the time:

export default function Book({id}) { 
  const [book, bookSet] = useState(false) 

  const loadBookFromServer = useCallback(async () => {
    let response = await fetch('api/book/' + id)
    response  = await response.json() 
    bookSet(response)
  }, [id]) // every time id changed, new book will be loaded

  useEffect(() => {
    loadBookFromServer()
  }, [loadBookFromServer]) // useEffect will run once and when id changes


  if (!book) return false //first render, when useEffect did't triggered yet we will return false

  return <div>{JSON.stringify(book)}</div>  
}

Suggestion : 2

The infinite loop is fixed by correct management of the useEffect(callback, dependencies) dependencies argument. The best way to solve the problem of an infinite loop created by circular new objects creation is... to avoid using references to objects in the dependencies argument of useEffect(): Even if you set up correctly the useEffect() dependencies, still, you have to be careful when using objects as dependencies. A common case that generates an infinite loop is updating state in the side-effect without having any dependency argument at all:

A functional component contains an input element. Your job is to count and display how many times the input has changed.

A possible implementation of <CountInputChanges> component looks as follows:

jsximport { useEffect, useState } from 'react';function CountInputChanges() {  const [value, setValue] = useState('');  const [count, setCount] = useState(-1);  useEffect(() => setCount(count + 1));  const onChange = ({ target }) => setValue(target.value);  return (    <div>      <input type="text" value={value} onChange={onChange} />      <div>Number of changes: {count}</div>    </div>  )}

The demo shows that count state variable increases uncontrollably, even if you haven't typed anything into the input. That's an infinite loop.

The problem lays in the way useEffect() is used:

jsxuseEffect(() => setCount(count + 1));

Because you want the count to increment when value changes, you can simply add value as a dependency of the side-effect:

jsximport { useEffect, useState } from 'react';function CountInputChanges() {  const [value, setValue] = useState('');  const [count, setCount] = useState(-1);  useEffect(() => setCount(count + 1), [value]);  const onChange = ({ target }) => setValue(target.value);  return (    <div>      <input type="text" value={value} onChange={onChange} />      <div>Number of changes: {count}</div>    </div>  );}

For example, the following component CountSecrets watches the words the user types into the input, and as soon as the user types the special word 'secret', a counter of secrets is increased and displayed.

Here's a possible implementation of the component:

jsximport { useEffect, useState } from "react";function CountSecrets() {  const [secret, setSecret] = useState({ value: "", countSecrets: 0 });  useEffect(() => {    if (secret.value === 'secret') {      setSecret(s => ({...s, countSecrets: s.countSecrets + 1}));    }  }, [secret]);  const onChange = ({ target }) => {    setSecret(s => ({ ...s, value: target.value }));  };  return (    <div>      <input type="text" value={secret.value} onChange={onChange} />      <div>Number of secrets: {secret.countSecrets}</div>    </div>  );}

Why does it happen?

The secret object is used as a dependency of useEffect(..., [secret]). Inside the side-effect callback, as soon as the input value equals 'secret', the state updater function is called:

javascriptsetSecret(s => ({
   ...s,
   countSecrets: s.countSecrets + 1
}));

Suggestion : 3

To get rid of your infinite loop, simply use an empty dependency array like so: Passing an array variable into your dependencies will also run an infinite loop. Consider this code sample: As a result, this invokes setCount on every update cycle. This means that we now have an infinite loop Consequently, React calls the setCount Hook until your app encounters an Update Depth error. This introduces bugs and instability into your program

If your useEffect function does not contain any dependencies, an infinite loop will occur.

For example, look at the following code:

function App() {
  const [count, setCount] = useState(0); //initial value of this 
  useEffect(() => {
    setCount((count) => count + 1); //increment this Hook
  }); //no dependency array.
  return (
    <div className="App">
      <p> value of count: {count} </p>
    </div>
  );
}

To mitigate this problem, we have to use a dependency array. This tells React to call useEffect only if a particular value updates.

As the next step, append a blank array as a dependency like so:

useEffect(() => {
   setCount((count) => count + 1);
}, []); //empty array as second argument.

If you pass a method into your useEffect dependency array, React will throw an error, indicating that you have an infinite loop:

function App() {
  const [count, setCount] = useState(0);

  function logResult() {
    return 2 + 2;
  }
  useEffect(() => {
    setCount((count) => count + 1);
  }, [logResult]); //set our function as dependency
  return (
    <div className="App">
      <p> value of count: {count} </p> {/*Display the value of count*/}
    </div>
  );
}
5._
const [count, setCount] = useState(0); //iniital value will be 0.
const myArray = ["one", "two", "three"];

useEffect(() => {
   setCount((count) => count + 1); //just like before, increment the value of Count
}, [myArray]); //passing array variable into dependencies

To solve this problem, we can make use of a Hook. This returns a mutable object which ensures that the reference does not change:

const [count, setCount] = useState(0);
//extract the 'current' property and assign it a value
const {
   current: myArray
} = useRef(["one", "two", "three"]);

useEffect(() => {
   setCount((count) => count + 1);
}, [myArray]); //the reference value is stable, so no infinite loop

Suggestion : 4

Your first render runs, and because data is falsey, render returns null and kicks off useEffect The dependency array in useEffect lets you specify the conditions to trigger it. If you provide useEffect an empty dependency array, it'll run exactly once, as in this example (CodeSandbox link): Changing state will always cause a re-render. By default, useEffect always runs after render has run. This means if you don't include a dependency array when using useEffect to fetch data, and use useState to display it, you will always trigger another render after useEffect runs.

1._
import React, { useEffect, useState } from 'react';
export default function DataDisplayer() {  const [data, setData] = useState('');
  useEffect(() => {    const getData = async () => {      const response = await fetch(`https://swapi.dev/api/people/1/`);      const newData = await response.json();      setData(newData);    };
    getData();  }, []); //<-- This is the dependency array
  if (data) {    return <div>{data.name}</div>;  } else {    return null;  }}
2._
import React, { useEffect, useState } from 'react';
export default function DataDisplayer(props) {  const [data, setData] = useState('');
  useEffect(() => {    const getData = async () => {      const response = await fetch(`https://swapi.dev/api/people/${props.id}/`);      const newData = await response.json();      setData(newData);    };
    getData();  }); //<-- Notice the missing dependency array
  if (data) {    return <div>{data.name}</div>;  } else {    return null;  }}
3._
import React, { useEffect, useState } from 'react';
export default function DataDisplayer(props) {  const [data, setData] = useState('');
  useEffect(() => {    const getData = async () => {      const response = await fetch(`https://swapi.dev/api/people/${props.id}/`);      const newData = await response.json();      setData(newData);    };    getData();  }, [props.id]); //<-- This is the dependency array, with a variable
  if (data) {    return <div>{data.name}</div>;  } else {    return null;  }}

Suggestion : 5

I’m attempting to perform a mutation when a specific route is hit in my application. When using useEffect and passing through a mutation method as a dependancy, it enters an infinite loop. Yes, your suggestions do fix the problem but give the lint warning. But the proper way of doing this is to destructure the mutate from the mutation and provide that to the dependency array of use effect! const {mutate} = useMutation(someFunction) Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

const [myMutation] = useMutation(MY_MUTATION);
const Component = () => {
   React.useEffect(() => {
      myMutation();
   }, [myMutation]);
}

Suggestion : 6

Use of useEffect in React function component is common & frequent. Although useEffect is executed only once per render cycle, but you may have several state updates in your useEffect which cause a re-render. We always need to be careful & make sure we are not consuming too much memory. You can ideally put console.log() inside your useEffect() function and see how many times it is getting printed. The infinite loop is fixed by using dependency argument correctly while calling useEffect(callback, dependencies). In following example, adding [value] parameter as a dependency of useEffect(..., [value])  call, the "login" state variable is updated only when [value] is changed. Doing this will solve the infinite loop problem:-

1._
const [data, setData] = useState(null);
const [login, setLogin] = useState("githubusername");
useEffect(() => {
   console.log('useEffect()')
   fetch(`https://api.github.com/users/${login}`)
      .then(res => res.json())
      .then(setData)
      .catch(console.error);
});

Instead:-

useEffect(() => {
   console.log('useEffect()')
   fetch(`https://api.github.com/users/${login}`)
      .then(res => res.json())
      .then(setData)
      .catch(console.error);
}, [login]);

Instead:-

Also, If we pass an empty array to useEffect, it’ s only executed after the first render.

useEffect(() => {
   // this is only executed once
}, [])

Suggestion : 7

I am using react query select to transform my data into required form. But it is causing infinite loop https://tkdodo.eu/blog/react-query-data-transformations#3-using-the-select-option TestComponent: const ITEMS = [] const [totalItems, setTotalItems] = useState<ITEST[]>(ITEMS) const { fetchTestData } = useFetchHook() I tried like this but first time it is not providing data, am i missing anything select: React.useCallback( (data) => { const response: Object = data?.data?.data as object const testData = plainToInstance(TestObject, response) return testData }, [] )

function deriveTotalItems(data) {
   const dataItems = [...data]
   dataItems[0].value = data.x
   dataItems[1].value = data.y
   dataItems[2].value = data.z
   return [...dataItems]
}

const {
   fetchTestData
} = useFetchHook()

const totalItems = fetchTestData.data == null ? [] : deriveTotalItems(fetchTestData.data)