React hooks useEffect call API second time and first time calling API response also returning

Arpit picture Arpit · Jul 31, 2019 · Viewed 7k times · Source

I'm fetching API with useEffect and API responding correct like

{response: {message: "This is a image link", status: "success"}, error: null}

Second time when I'm receiving next API call response and If there is a error then old response is not removing. It's showing like this

{response: {message: "This is a image link", status: "success"}, error: TypeError}

I worked this tutorial code for fetching API

Check my exact code in codesandbox

import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";

const useFetch = (url, options) => {
  console.log("useFetch");
  const [response, setResponse] = React.useState(null);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    const FetchData = async () => {
      try {
        const res = await fetch(url, options);
        const json = await res.json();
        setResponse(json);
        // console.log("json - ", json);
      } catch (error) {
        // console.log("error - ", error);
        setError(error);
      }
    };
    FetchData();
  }, [url]);
  return { response, error };
};

function App() {
  const API_URL = `https://dog.ceo/api/breeds/image/random`;
  const [apiUrl, setApiUrl] = useState(API_URL);
  const res = useFetch(apiUrl, {});
  console.log("res - ", res);
  if (!res.response) {
    return <div>Loading...</div>;
  }

  const apiCallAgain = () => {
    const apiUrl = `https://d.ceo/api/breeds/image/q`;
    setApiUrl(apiUrl);
  };
  const dogName = res.response.status;
  const imageUrl = res.response.message;
  return (
    <div className="App">
      <div>
        <button onClick={apiCallAgain}>API Call</button>
        <h3>{dogName}</h3>
        <div>
          <img src={imageUrl} alt="avatar" />
        </div>
      </div>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

open console and check res object by default. Now click on API Call button and check res object again. When I'm using useState null in starting then why old state is showing?

Answer

Agney picture Agney · Jul 31, 2019

When a component rerenders, hooks do not reinitialize all state in them. State cannot not work if that were the case (component re-renders when you change state).

Instead for each render, useState hook stores and returns it's specific value for that render. In your case when the API_URL changes, useEffect reruns, but the state variables don't.

From the docs:

This is a way to “preserve” some values between the function calls — useState is a new way to use the exact same capabilities that this.state provides in a class. Normally, variables “disappear” when the function exits but state variables are preserved by React.

A fix for this would be to reset the state for response and error in useEffect:

 React.useEffect(() => {
    setResponse(null);
    setError(null);
    const FetchData = async () => {
      try {
        const res = await fetch(url, options);
        const json = await res.json();
        setResponse(json);
        // console.log("json - ", json);
      } catch (error) {
        // console.log("error - ", error);
        setError(error);
      }
    };
    FetchData();
  }, [url]);