Search code examples
javascriptreactjsaws-amplify

React useEffect infinite loop


I was experimenting with React a little and wanted to retrieve a json file I had on S3 bucket. I'm using the next code

import React, { useState, useEffect } from "react";
import { Storage } from "aws-amplify";

export default function Gallery(props){
  const [picList, setPicList] = useState([]);

  useEffect(() => {
    async function onLoad() {
      try{
        const picUrl = await Storage.get('list.json');
        const picr = await fetch(picUrl);
        const picjs = await picr.json();
        setPicList(picjs);
        console.log(picList);
      }
      catch(e){
        alert(e);
      }
    };
    onLoad();
  }, [picList]);



  function renderLander() {
    return (
      <div className="lander">
        <h1>Lander</h1>
      </div>
    );
  }

  return(
    <div className="Gallery">
      {renderLander()}
    </div>
  );
}

In the begining I was getting the error that when using setPicList, picList doesn't take the value of json file. I'm using console.log() to debug, so I'm sure that Storage.get is getting the right url, fetch() is getting the file json, but picList prints the default value [].

Now, with the code above instead I'm getting an infinite loop, but this time the value of picList is getting the right value (but it keeps fetching S3 for every render I think).

I was following this guide

Edit: for completeness, picList is not getting updated if I remove the dependency, ie if I let [] instead of [picList].


Solution

  • As Brian mentioned before, your useEffect is set to trigger whenever picList is changed. Since picList is being changed inside useEffect, this creates an infinite loop.

    Since you're retrieving an object from S3, all you need to do is retrieve it once.

    useEffect(() => {
        ...
    }, []);
    

    Ensures that the useEffect will only run once, when first rendered

    React hooks changes states asynchronously, so console.logging right after will produce either None or the previous value.

    What you need to do is add a conditional statement to your render function, to re-render the component when the state updates.

    function renderLander() {
        return (
          <div className="lander">
            <h1>Lander</h1>
          </div>
        );
      }
    
      return(
        <div className="Gallery">
          {picList ? renderLander() : null}
        </div>
      );