TransWikia.com

react - Infiinite loop when sending axios request (useState problem?)

Stack Overflow Asked by Countour-Integral on January 5, 2022

First I declare these two varaibles to get and set the values I get from the axios request

let tmpFolder = [];
const [folder_JSX,setFolder_JSX] = useState([])

then I send the request

const sendRequest = () => {
  return axios.get(`sample_url`).then(response => {return response.data})
}

sendRequest().then(folder=> {
//loop through each item and append a JSX element into the array
for (let i=0;i <folder.length;i++) {
  tmpFolder.push(<Folder tags={folder[i].tags} name={folder[i].name} description={folder[i].description} date={folder[i].date_created[0]} tagOne={folder[i].tags[0]} tagTwo={folder[i].tags[1]} tagThree={folder[i].tags[2]} tagRest={folder[i].tags.length - 3} />)
}
setFolder_JSX(prev => tmpFolder) // <----- This line is causing an infinite loop
}).catch(err => console.log(err))

The compiler throws Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

which is True (when I remove one prop from the Folder JSX element it does not throw this error but keeps making requests infinitely)

  • First I loop through all the items in the response and then

    – I set the folder state to that array.

The folder_JSX (which gets rendered in the dom) does not change, but the requests keep getting sent.

I can’t figure out what is causing the infinite loop

2 Answers

Every time the state updates, your component function is run again. Because you are calling sendRequest each time the function is called, and then updating the state based on it, you get an endless cycle of rerenders.

You shouldn't be performing side effects - such as API calls - inside the main body of your function anyway. (If you've previously used class components in React, you should think of your function component as the equivalent of render, which just computes the JSX output from the component's current props and state, and should perform no side effects.) When using hooks, you have the useEffect hook for things like this. And in particular, its second argument is an array of variables, for which the effect will only run when one of those values has changed since the last render. This is how you can prevent the effect running on each render - which you must prevent here to stop the infinite updates.

As far as I can tell, this request doesn't depend on any internal state, so it should probably only run when the component first renders. In that case, pass the empty array as second argument.

In short, you should rewrite your code like this:

useEffect(() => sendRequest().then(folder => {
  //loop through each item and append a JSX element into the array
  for (let i=0;i <folder.length;i++) {
    tmpFolder.push(<Folder tags={folder[i].tags} name={folder[i].name} description={folder[i].description} date={folder[i].date_created[0]} tagOne={folder[i].tags[0]} tagTwo={folder[i].tags[1]} tagThree={folder[i].tags[2]} tagRest={folder[i].tags.length - 3} />)
  }
  setFolder_JSX(prev => tmpFolder)
}).catch(err => console.log(err)), []);

Answered by Robin Zigmond on January 5, 2022

It appears as though you have the fetch logic out in the open in the functional component body, something like:

const MyComponent = () => {
  let tmpFolder = [];
  const [folder_JSX, setFolder_JSX] = useState([]);

  const sendRequest = () => {
    return axios.get(`sample_url`).then(response => {
      return response.data;
    });
  };

  sendRequest() // <-- invoked each render cycle
    .then(folder => {
      //loop through each item and append a JSX element into the array
      for (let i = 0; i < folder.length; i++) {
        tmpFolder.push(
          <Folder
            tags={folder[i].tags}
            name={folder[i].name}
            description={folder[i].description}
            date={folder[i].date_created[0]}
            tagOne={folder[i].tags[0]}
            tagTwo={folder[i].tags[1]}
            tagThree={folder[i].tags[2]}
            tagRest={folder[i].tags.length - 3}
          />
        );
      }
      setFolder_JSX(prev => tmpFolder); // <-- state update triggers rerender
    })
    .catch(err => console.log(err));

  ...
};

If this is the case then sendRequest is invoked each render cycle and ultimately updates component state which triggers another render, thus the infinite cycle.

Perhaps you wanted to only fetch data when the component mounts, you can use an effect to do any side-effects:

const MyComponent = () => {
  let tmpFolder = [];
  const [folder_JSX, setFolder_JSX] = useState([]);

  useEffect(() => {
    const sendRequest = () => {
      return axios.get(`sample_url`).then(response => {
        return response.data;
      });
    };
  
    sendRequest()
      .then(folder => {
        //loop through each item and append a JSX element into the array
        for (let i = 0; i < folder.length; i++) {
          tmpFolder.push(
            <Folder
              tags={folder[i].tags}
              name={folder[i].name}
              description={folder[i].description}
              date={folder[i].date_created[0]}
              tagOne={folder[i].tags[0]}
              tagTwo={folder[i].tags[1]}
              tagThree={folder[i].tags[2]}
              tagRest={folder[i].tags.length - 3}
            />
          );
        }
        setFolder_JSX(prev => tmpFolder);
      })
      .catch(err => console.log(err));
  }, []); // <-- empty dependency to run once on component mount

  ...
};

Answered by Drew Reese on January 5, 2022

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP