React setState not working with useEffect on fetched data

Viewed 716

My aim is to get the Data from an API call on first rendering of the page, store it in a state variable for further use. During the call, a loader keeps spinning and when call is successful, the loader disappears and the data is rendered on the screen using the state variable in which the response is saved.

Currently, I successfully make the call and get the data but when I try to store the data on a state variable, it remains undefined. I know that it is due to the fact that I have not initiated it with anything when I declared it. My concern being when I set it later to the response of the API call then why is it undefined? Also if it is undefined then the loader would be running infinitely but in reality it stops and certain part of data is shown.

I have looked at various solution on Stack overflow and tried the following:

  • Check the state variable is not empty before rendering
  • used useLayoutEffect instead of useEffect
  • put the call function inside useEffect

Nothing works and the loader is stuck on loading. Thank You.

import React, { useState, useEffect } from "react";
import * as S from "./Service.js";
import { Loader } from "./Loader.js";

function NeedHelp() {
  let rspnse;
  const [loader, setLoader] = useState(true);
  const [data, setData] = useState(); // not setting any value

  async function fetchData() {
    rspnse = await S.getData();
    setData(rspnse);
    console.log("data :\n",data);
  }
  useEffect(() => {
    setLoader(true);
    fetchData();
    setLoader(false);
  }, []);

  return (
    <div className=" pt-3 p-1 mx-1 w-full">
      {loader || typeof data === "undefined" ? (
        <Loader />
      ) : (
        data && (
          <div>
            <div id="first row" className="border p-2">
              <span className="px-4 px-4 flex border">
                <input
                  className="w-1/5 text-xs rounded py-1 px-1.5 placeholder-gray placeholder-opacity-75 focus:ring-0.5 focus:ring-blue focus:border-blue"
                  type="text"
                  placeholder="Filter..."
                ></input>
                <div id="search_clear" className=" mx-1">
                  <button
                    onClick=""
                    className="text-white rounded px-1.5 py-1 mx-0.5 bg-blue transition duration-500 ease-in-out transform hover:translate-x-1 hover:scale-110 hover:bg-blue hover:shadow-lg"
                  >
                    Clear
                  </button>
                </div>
                <div className="self-center">
                  <p className="text-sm text-bgray ml-5">
                    Number of View: {data.data.length}
                  </p>
                </div>
              </span>
            </div>
            <div id="table">
              <table className="table_auto  w-full">
                <thead>
                  <tr className="">
                    <th className=" "> Name</th>
                    <th className=" "> ID</th>
                    <td className=" "></td>
                    <td className=" "></td>
                  </tr>
                </thead>
                <tbody className="border">
                  <tr>
                    <td className="border">1</td>
                    <td className="border">2</td>
                    <td className="border">3</td>
                    <td className="border">4</td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>
        )
      )}
    </div>
  );
}

export default NeedHelp;

2 Answers

No reason to store rsponse as you have, and I cleaned the code up in a couple of other ways that aren't super notable, except maybe that loading will always be shown if no data is set. This should without any hitches, if it doesn't further debugging of what res is needs to be done:

import React, { useState, useEffect } from "react";
import * as S from "./Service.js";
import { Loader } from "./Loader.js";

function NeedHelp() {
  const [data, setData] = useState(); // not setting any value

  useEffect(() => {
    (async () => {
      setData(await S.getData());
    })();
  }, []);

  return (
    <div className=" pt-3 p-1 mx-1 w-full">
      {!data ? (
        <Loader />
      ) : (
          <div>
            <div id="first row" className="border p-2">
              <span className="px-4 px-4 flex border">
                <input
                  className="w-1/5 text-xs rounded py-1 px-1.5 placeholder-gray placeholder-opacity-75 focus:ring-0.5 focus:ring-blue focus:border-blue"
                  type="text"
                  placeholder="Filter..."
                ></input>
                <div id="search_clear" className=" mx-1">
                  <button
                    onClick=""
                    className="text-white rounded px-1.5 py-1 mx-0.5 bg-blue transition duration-500 ease-in-out transform hover:translate-x-1 hover:scale-110 hover:bg-blue hover:shadow-lg"
                  >
                    Clear
                  </button>
                </div>
                <div className="self-center">
                  <p className="text-sm text-bgray ml-5">
                    Number of View: {data.data.length}
                  </p>
                </div>
              </span>
            </div>
            <div id="table">
              <table className="table_auto  w-full">
                <thead>
                  <tr className="">
                    <th className=" "> Name</th>
                    <th className=" "> ID</th>
                    <td className=" "></td>
                    <td className=" "></td>
                  </tr>
                </thead>
                <tbody className="border">
                  <tr>
                    <td className="border">1</td>
                    <td className="border">2</td>
                    <td className="border">3</td>
                    <td className="border">4</td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>
      )}
    </div>
  );
}

export default NeedHelp;

modify your code as below

const [loader, setLoader] = useState(false);
  
  async function fetchData() {
    setLoader(true);
    rspnse = await S.getData();
    setData(rspnse);
    console.log("data :\n",data);
    setLoader(false);
  }
  useEffect(() => {
      fetchData();
     }, []);
Related