-1

I have made two simple straight forward component is React, used a open source API to test API integration. React is showing this weird behavior of infinite console logs. I don't understand the issue. I'm using the fetch function for making API calls and functional component.

App component:

function App() {

 const [characters, setCharac] = useState([])

  const URL = "https://swapi.dev/api/";

   fetch(URL + "people").then(response => response.json().then(data => {
     setCharac(data.results)
     console.log('Test');
   }))

  return (
    <div className="App">
      {characters.map(charac => {
        return <Character {...charac} />
      })}
    </div>
  );
}

Character component:

const Character = (props) => {
  console.log(props);
  return (
    <div key={props.name}>
      <h1>{props.name}</h1>
      <p>{props.height}</p>
    </div>
  );

}

console.log('Test'); in App component and console.log(props); in Character component are being executed infinitely.

This is the render method

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
Ahmed Javed
  • 60
  • 1
  • 9
  • Your `fetch` call has a "side effect" - it changes some state when the call finishes. Because of this, you shouldn't have this happen on its own, it should be run from within a [`useEffect` hook](https://reactjs.org/docs/hooks-effect.html). The [beta docs](https://beta-reactjs-org-git-effects-fbopensource.vercel.app/learn/synchronizing-with-effects) also have info on this hook. – romellem May 24 '22 at 19:58
  • @Ahmed Javed Every time you do "setCharac" you component will go through re-render and again fetch will be called again. – Ravi Ojha May 24 '22 at 20:00
  • even if I do it using useEffect it shows the same behavior. – Ahmed Javed May 24 '22 at 20:00
  • use another function to setState inside `useEffect`. Do not use setState directly inside the `useEffect` – besjon_c May 24 '22 at 20:04

4 Answers4

1

Try wrapping it in a useEffect

e.g.

useEffect(()=>{
fetch(URL + "people").then(response => response.json().then(data => {
     setCharac(data.results)
     console.log('Test');
   }))
},[])

otherwise every time the state is set it is firing off the fetch again because a re-render is being triggered.

Damian Green
  • 6,895
  • 2
  • 31
  • 43
1

Your components are rendering multiple times because your state is changed every time you fetch data (because of setState).

Try creating a function fetchData(). Make this function async as well to wait for data to be retrieved.

const fetchData = async () => {
   const result = await fetch(URL + "people").then(response => response.json().then(data => {
    setCharac(data.results)
    console.log('Test');
    return data;
   }));
   return result;
}

and then use it inside useEffect (Read more about useEffects: React hooks: What/Why `useEffect`?)

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

Note the usage of [] in useEffect. The data will be fetched only once when you load the component.

besjon_c
  • 170
  • 2
  • 12
  • 1
    Thanks, that worked! usage of [] is really weird. I wasn't passing it earlier. – Ahmed Javed May 24 '22 at 20:07
  • Happy to help, this explains the `useEffect` behaviour https://stackoverflow.com/questions/62631053/useeffect-being-called-multiple-times – besjon_c May 24 '22 at 20:09
1

Because you fetch some data, update the state, which causes a re-render, which does another fetch, updates the state, which causes another render...etc.

Call your fetch function from inside a useEffect with an empty dependency array so that it only gets called once when the component is initially rendered.

Note 1: you can't immediately log the state after setting it as setting the state is an async process. You can, however, use another useEffect to watch for changes in the state, and log its updated value.

Note 2: I've used async/await in this example as the syntax is a little cleaner.

// Fetch the data and set the state
async function getData(endpoint) {
  const json = await fetch(`${endpoint}/people`);
  const data = await response.json();
  setCharac(data.results);
}

// Call `getData` when the component initially renders
useEffect(() => {
  const endpoint = 'https://swapi.dev/api';
  getData(endpoint);
}, []);

// Watch for a change in the character state, and log it
useEffect(() => console.log(characters), [characters]);
Andy
  • 61,948
  • 13
  • 68
  • 95
1

You can do something like this:

import React, { useState, useEffect, useCallback } from "react";

const Character = (props) => {
  console.log(props);
  return (
    <div key={props.name}>
      <h1>{props.name}</h1>
      <p>{props.height}</p>
    </div>
  );
};

export default function App() {
  const [characters, setCharac] = useState([]);

  const makeFetch = useCallback(() => {
    const URL = "https://swapi.dev/api/";
    fetch(URL + "people").then((response) =>
      response.json().then((data) => {
        setCharac(data.results);
        console.log("Test");
      })
    );
  }, []);

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

  return (
    <div className="App">
      {characters.map((charac) => {
        return <Character {...charac} />;
      })}
    </div>
  );
}

Ravi Ojha
  • 168
  • 1
  • 13