Search code examples
asynchronousreact-hooksuse-effect

useEffect() leads to infinite loop on api call


I have read all the docs, found situations similar to mine but never found a solution that applies.

This component receives a prop from the parent props.measurementsArray which it sends to an API in useEffect, storing the results in state (setCentile_data). The measurementsArray is made up of objects (each containing growth data) and I loop through the array, making an API call for each, adding the results to centile_data.

function ChartData(props) {

  const [isLoading, setLoading] = useState(true);
  const [centile_data, setCentile_data] = useState([]);

  const measurementsArray = props.measurementsArray;
  const reference = props.reference;

  useEffect(() => {
    const fetchCentilesForMeasurement = async  (payload, reference) =>{
    
      let url
      if (reference === "uk-who"){
        url = `${process.env.REACT_APP_GROWTH_API_BASEURL}/uk-who/calculation`
      }
      if (reference === "turner"){
        url = `${process.env.REACT_APP_GROWTH_API_BASEURL}/turner/calculation`
      }
      if (reference === "trisomy-21"){
        url = `${process.env.REACT_APP_GROWTH_API_BASEURL}/trisomy-21/calculation`
      }
  
      const response = await axios({
        url: url,
        data: payload,
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
      });
  
      return response.data;
    }

    let ignore = false; // this prevents data being added to state if unmounted
    if (measurementsArray.length > 0) {
      try {
        measurementsArray.forEach(measurement => {
          fetchCentilesForMeasurement(measurement, reference).then((result) => {
            if (!ignore) {
              // this prevents data being added to state if unmounted
              setCentile_data(centile_data => [...centile_data, result])
            }
          }).then(()=>{
            setLoading(false)
          }).catch((error)=>{
            console.log(error.message);
            setCentile_data([])
            setLoading(false)
          });
        });
      } catch (error) {
        console.error("Failure!");
        console.error(error.response.status);
        alert("The server is not responding. Sorry.");
        if (!ignore) {
          setLoading(false);
        }
      }
    } else {
      if (!ignore) {
        setLoading(false);
      }
    }
    return () => {
      ignore = true;
    }; // this prevents data being added to state if unmounted
  }, []);

render (...)

The difficulty is that I am setting centile_data in useEffect which causes the component to rerender, creating a loop. Passing in dependencies suggested by the linter (as per the documentation), or [] has no effect. I would be grateful if someone could set me straight about the howling error I am making.


Solution

  • So my apologies. This code it turns out is fine but there was an error in a component further down the tree. Life lesson for me, sorry to waste time.