Joe Cillo
Joe Cillo

Reputation: 37

useEffect causing infinite loop or getting errors

I am trying to learn React hooks. I'm creating a simple news app that leverages the NY times api.

When I leave the dependency empty, nothing loads and when I use data as the dependency it goes into an infinite loop.

When I use isLoading, it works but then I receive an error "localhost/:1 Unchecked runtime.lastError: The message port closed before a response was received." and "localhost/:1 Error handling response: TypeError: Cannot read property 'level' of undefined"

main.js

import React, { useEffect, useState } from "react";
import { nyTimesApi } from "../services/Api";
const Main = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [data, setData] = useState([]);

  const fetchData = async () => {
    const result = await nyTimesApi();
    setData(result);
    setIsLoading(false);
    console.log(data.results);
  };
 
  useEffect(() => {
    fetchData();
  }, [isLoading]);
  return <div className="main">work</div>;
};
export default Main;

I am also receiving a warning, when using isLoading, in the terminal saying "React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps"

What am I doing wrong?

Upvotes: 1

Views: 9872

Answers (2)

Jacob Smit
Jacob Smit

Reputation: 2379

The infinite loop is caused by the combination of using setData(result) and [data]:

  1. The component mounts and the useEffect is run.
  2. setData(result) will asynchronously update the data value and trigger a rerender.
  3. During the rerender the useEffect will be run again as data will not successfully complete the reference comparison.
  4. Repeat 2 to 3.

The warning "React Hook useEffect has a missing dependency" is self explanatory to an extent.

Making use of an external (to the useEffect) variable that is not included in the dependency array may mean that the value of the variable changes and the useEffect will not be retriggered or that the value may not be the expected value.

Below is a an example of how the original snippet might be fixed:

import React, { useEffect, useState } from "react";
import { nyTimesApi } from "../services/Api";

const Main = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [data, setData] = useState([]);
 
  useEffect(() => {
    // Create function inside useEffect so that the function is only
    // created everytime the useEffect runs and not every render.
    const fetchData = async () => {
        const result = await nyTimesApi();
        setData(result);
        setIsLoading(false);

        // setData will update state asynchronously.
        // Log the value stored instead.
        console.log(result.results);
    };

    //Run data fetching function.
    fetchData();

  }, 
  // Both of these are set functions created by useState and will
  // not change for the life of the component, but adding them to
  // the dependency array will make your linter happy.

  // Do not need to check isLoading as it is always true on component
  // mount.
  [setData, setIsLoading]);

  return <div className="main">work</div>;
};

export default Main;

Upvotes: 2

peteredhead
peteredhead

Reputation: 2804

The second argument to useEffect is an array of variables, which trigger the function within useEffect to be called every time they change.

You have [isLoading] as the second argument to useEffect and update the value of this within fetchData(). This is going to cause the useEffect trigger to happen again and again and again.

If you only want to have useEffect call once (in a similar way to ComponentDidMount in class-based components), then you need to specify an empty array as the second argument.

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

Upvotes: 1

Related Questions