Home > Software design >  useState returned undefined after page refresh
useState returned undefined after page refresh

Time:01-12

useState hook returned undefined, empty array [], and/or empty object {} after page refresh but returned actual data when I resave the code.

The data needs to always be available on every refresh as the data is stored in localStorage.

Here is the code

const Admin = () => {
  const { adminId } = useParams();
  const navigate = useNavigate();

  const [userId, setUserId] = useState<string>("");
  const [results, setResults] = useState(false);
  const [studentEnglishResults, setStudentEnglishResults] = useState<{
    [key: string]: string;
  }>({});
  const [englishResults, setEnglishResults] = useState<string[]>([]);

  const adminUser = adminUsers.find(
    (adminUser) => adminUser.adminId === adminId
  );

  const userFound = users.find((user) => user.userId === userId);

  useEffect(() => {
    if (adminUser?.adminId !== adminId) {
      navigate("/");
      return
    }
  }, []);

  useEffect(() => {
    if (localStorage.length > 0) {
      setUserId(JSON.parse(localStorage.getItem("userId") as string));

      setResults(true);
      setStudentEnglishResults(
        JSON.parse(localStorage.getItem("englishAnswers") as string)
      );
      setEnglishResults(Object.keys(studentEnglishResults));
    } else {
      setResults(false);
    }

    console.log(userId); // This becomes undefined on every page refresh
    console.log(studentEnglishResults); // This becomes empty {}
    console.log(englishResults); // This becomes empty []
  }, []);

  useEffect(() => {
    if (userFound) {
      console.log(userFound); //This shows on code re-save
    } else {
      console.log("user not found"); //This shows on page refresh
    }
  }, []);

  const generateResult = () => {};

  const clearResult = () => {
    localStorage.clear();
  };

  const backToLogin = () => {
    navigate("/");
  };

  return (
    <>
      <div className="bg-gray-400 mx-auto w-3/4 p-3 mt-5">
        <p className="capitalize font-serif text-center">
          <span className="font-bold uppercase text-white text-2xl">
            student result checker
          </span>
        </p>
      </div>
      <div className="my-10 flex justify-center gap-3 text-white">
        <button
          type="button"
          className="btn bg-green-500"
          onClick={generateResult}
        >
          Generate Results
        </button>

        <button type="button" className="btn bg-blue-500" onClick={backToLogin}>
          Login
        </button>
      </div>
      {results ? (
        <div>
          <div className="shadow-md shadow-gray-500 w-2/4 mx-auto p-5">
            <p className="text-2xl text-gray-500">Answers selected by: </p>
            <table className="table-auto border-collapse border w-full">
              <thead>
                <tr>
                  <th className="text-left">Question</th>
                  <th className="text-left">Answer</th>
                </tr>
              </thead>
              <tbody>
                {englishResults.map((item, index) => {
                  return (
                    <tr key={index}>
                      <td className="">{item}</td>
                      <td>{studentEnglishResults[item]}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
          <div className="w-2/4 mx-auto mt-50 p-5">
            <button
              type="button"
              className="btn bg-red-600 text-white"
              onClick={clearResult}
            >
              Clear Results
            </button>
          </div>
        </div>
      ) : (
        <div className="w-2/4 bg-gray-400 p-5 mx-auto rounded-md">
          <p className="text-4xl text-white text-center">
            There is no result to display
          </p>
        </div>
      )}
    </>
  );
};

export default Admin;

How do I retain the data after refresh??

CodePudding user response:

There is no need for a useEffect in this case, just use useState lazy initializer :

const [userId, setUserId] = useState(() => {
    const uid = localStorage.getItem("userId");
    return uid && JSON.parse(uid);
});
// ...

CodePudding user response:

I think you need to move the code that sets the state variables from localStorage into a separate useEffect hook that listens to changes in localStorage.

an example to this is to to pass a dependency array to the useEffect hook that includes localstorage like this:

useEffect(() => {
    if (localStorage.length > 0) {
      setUserId(JSON.parse(localStorage.getItem("userId") as string));

      setResults(true);
      setStudentEnglishResults(
        JSON.parse(localStorage.getItem("englishAnswers") as string)
      );
      setEnglishResults(Object.keys(studentEnglishResults));
    } else {
      setResults(false);
    }
  }, [localStorage]);

By the way, you need to ensure that you are setting the value of localstorage when you update the state.

useEffect(() => {
    localStorage.setItem("userId", JSON.stringify(userId));
    localStorage.setItem("englishAnswers", JSON.stringify(studentEnglishResults));
  }, [userId, studentEnglishResults]);

Edit : As a good practice, use useEffect with a cleanup function to avoid memory leak when using 'navigate' inside the hook

useEffect(() => {
    if (adminUser?.adminId !== adminId) {
      const cleanup = () => {
        navigate("/");
      };
      return cleanup;
    }
  }, [adminUser]);
  • Related