Call useEffect and re-render every time I call the same API

Viewed 1236

On every call of the API I get a new image link (https://dog.ceo/api/breeds/image/random this is the API I am calling), I am using useEffect hook and calling the API.

Every time I click the button I want to hit the API, get the new value, and re-render the image. When I pass "dog" as a dependency to useEffect, I go into an infinite loop, all I want is when I click on the button I want to update the image.

  const [dog, setDog] = useState('')

  useEffect(() => {

    const getDogs = async () => {
      const dogsFromServer = await fetchDogs()
      setDog(dogsFromServer.message)
    }
    getDogs()
  }, [])

  const fetchDogs = async () => {
    const res = await fetch('https://dog.ceo/api/breeds/image/random')
    const data = await res.json()
    console.log(data.message)
    return data
  }

  const changeDog = ()=> {
    alert("changeddog")
  }

  return (
    <div className="App">
      <img src={dog}/>
      <button onClick={()=>fetchDogs()}>Change Image<button/>
    </div>
  );
3 Answers

I would move the dog state updating also into the fetchDogs function, you can call from the useEffect and from the button's onClick handler.

const [dog, setDog] = useState('');

useEffect(() => {
  fetchDogs(); // <-- load dog state on initial render
}, []);

const fetchDogs = async () => {
  try {
    const res = await fetch('https://dog.ceo/api/breeds/image/random');
    const data = await res.json();
    console.log(data.message);
    setDog(data.message);
  } catch (error) {
    // log error, etc..
  }
};

const changeDog = ()=> {
  alert("changeddog")
}

return (
  <div className="App">
    <img src={dog}/>
    <button
      onClick={fetchDogs} // <-- load on button click
    >
      Change Image
    <button/>
  </div>
);

Instead of fetching the data on the useEffect, you must do it on the click event. Something like this:

function YourComponent(){
  const [dog, setDog] = useState('');

  const fetchDogs = async () => {
    const res = await fetch('https://dog.ceo/api/breeds/image/random');
    const data = await res.json();
    return data;
  };

  const handleGetDogs = async () => {
    const dogsFromServer = await fetchDogs();
    setDog(dogsFromServer.message);
  };

  return (
    <div className="App">
      <img src={dog}/>
      <button onClick={handleGetDogs}>Change Image<button/>
    </div>
  );

}

First, move the fetchDogs function outside of your component and move the getDogs definition outside of the useEffect, then call getDogs from changeDog and use that function in the button onClick:

const fetchDogs = async () => {
    const res = await fetch('https://dog.ceo/api/breeds/image/random');
    const data = await res.json();
    console.log(data.message);
    return data;
});

function App() {
  const [dog, setDog] = useState('');

  const getDogs = async () => {
    const dogsFromServer = await fetchDogs();
    setDog(dogsFromServer.message);
  }

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

  
  const changeDog = ()=> {
    // alert("changeddog");
    getDogs();
  }

  return (
    <div className="App">
      <img src={dog}/>
      <button onClick={changeDog}>Change Image<button/>
    </div>
  );
}

Working example:

Edit flamboyant-drake-suv0w

Related