'useEffect keeps fetching data and re-rendering the component

I just started building projects this week and fresh outta tutorial hell so please go easy, my code might be rough too.

I'm trying to render a "randomAdvice" in the component on button click, but it just keep re-rendering and making API calls.

This doesn't happen when I console log, only when I try to add the Advice to the component to be displayed.

code is below

function App() {
  const [randomAdvice, setRandomAdvice] = useState({
    id: '',
    advice: '',
  });

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('https://api.adviceslip.com/advice');
      const data = await response.json();

      setRandomAdvice(data);
    };

    fetchData();
  }, [randomAdvice]);

  const setAdvice = () => {
    setRandomAdvice({ ...randomAdvice, advice: randomAdvice.slip.advice });
  };
  const setId = () => {
    setRandomAdvice({ ...randomAdvice, advice: randomAdvice.slip.id });
  };

  const getAdvice = () => {
    setAdvice();
    setId();
    console.log(randomAdvice.slip.advice, randomAdvice.slip.id);
  };
// work well when I click the button it gets logged to the console but once I try to uncomment so it renders on the dom, it just keeps re-rendering



return (
    <div className='App'>
      <p>How far</p>
      {/* {randomAdvice && <p>{randomAdvice.slip.advice}</p>}
      {randomAdvice && <p>{randomAdvice.slip.id}</p>} */}
      <button onClick={getAdvice}>generate random advice</button>
    </div>
  );



Solution 1:[1]

Your code produces an infinite re-rendering due to useEffect implementation. You are updating a state which is also a dependency of your useEffect hence it triggers a re-render.

I have updated your code to prevent the issue:

function App() {
  const [randomAdvice, setRandomAdvice] = useState({
    id: '',
    advice: '',
  });

  const fetchData = async () => {
    const response = await fetch('https://api.adviceslip.com/advice');
    const { slip } = await response.json();

    setRandomAdvice(slip);
  };

  const getAdvice = () => {
    fetchData();
  };

  return (
    <div className="App">
      <p>How far</p>
      {randomAdvice && <p>{randomAdvice.advice}</p>}
      {randomAdvice && <p>{randomAdvice.id}</p>}
      <button onClick={getAdvice}>generate random advice</button>
    </div>
  );
}

Also, make sure to have a handler when a user clicks the button to prevent sending multiple requests at the same time. For example, setting it to disabled once the promise is not yet resolved.

EDIT: I have also deconstructed slip from response to match with your state structure.


EDIT: You can also simplify your return statement using the code below:

<div className="App">
  <p>How far</p>
  {randomAdvice && (
    <>
      <p>{randomAdvice.advice}</p>
      <p>{randomAdvice.id}</p>
    </>
  )}
  <button onClick={getAdvice}>generate random advice</button>
</div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1