Search code examples
google-cloud-firestorenext.jsnext-auth

NextJS 13 return block renders before useEffect triggers


I'm building a Next.js 13 app that uses next-auth for sessions/loading and queries Firebase Firestore to check if a user exists in the database. If the user exists, the app should redirect them to a new page. Otherwise, it should render a terms and conditions form.

The problem I am facing is that the form will get rendered for ~2 seconds before the useEffect function confirms whether or not the user already exists.

Below is the relevant code:

//This is a Next.js page component that presents a list of rules that users have to agree
//to before using the platform. The component checks whether the current user has accepted
//the rules before and redirects them to the user profile page if they have. Otherwise,
//the user is presented with the list of rules and checkboxes to accept them. Once the
//user has accepted the rules, they are redirected to their user profile page.
"use client";
import { useSession } from "next-auth/react";
import {
  collection,
  query,
  where,
  getDocs,
  addDoc,
  updateDoc,
  doc,
  getDoc,
} from "firebase/firestore";
import { db } from "@/lib/firebaseConfig";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Press_Start_2P } from "next/font/google";
import LoadingCircle from "@/components/LoadingCircle";
import { useAuth } from "@/components/AuthContext";

const p2 = Press_Start_2P({ weight: "400", subsets: ["latin"] });

function Rules() {
  const { data: session } = useSession();
  const router = useRouter();
  const { user, loading } = useAuth();
  const [existingUser, setExistingUser] = useState(false);
  const [isChecked, setIsChecked] = useState([
    false,
    false,
    false,
    false,
    false,
  ]);

  const checkExistingUser = async () => {
    const docRef = doc(db, "users", user);
    const docSnap = await getDoc(docRef);
    if (docSnap.data().rulesAccepted === true) {
      setExistingUser(true);
    } else {
      setExistingUser(false);
    }
  };

  useEffect(() => {
    if (user) {
      checkExistingUser();
    }
  }, [user]);

  //// If the user accepted the rules before
  // they are redirected to their user profile page
  useEffect(() => {
    if (existingUser) {
      router.push(`/user/${user}/`);
    }
  }, [existingUser]);

  const addNewUser = async () => {
    const docRef = doc(db, "users", user);
    await updateDoc(docRef, { rulesAccepted: true });
    router.push(`/user/${user}/NewUser/`);
  };

  const handleCheckboxChange = (index) => {
    const newChecked = [...isChecked];
    newChecked[index] = !newChecked[index];
    setIsChecked(newChecked);
  };

  const isAllChecked = isChecked.every((checked) => checked);

  if (loading) {
    return <LoadingCircle />;
  }

  return (
    <div className="mt-32 md:mt-64 bg-black shadow-md">
      <div className=" text-white mx-auto mt-3 max-w-md flex flex-col items-left space-y-4 p-5">
        <p className="text-xl text-[#21FF7E]">
          Finding the right co-founder is a serious endeavour. Please confirm
          that you have read and agree to the following rules:
        </p>
        <label className="flex items-center">
          <input
            type="checkbox"
            checked={isChecked[0]}
            onChange={() => handleCheckboxChange(0)}
            className="h-7 w-7"
          />

          <span className="ml-10 max-w-xs">
            I confirm that I am searching for a co-founder. (This means someone
            with at least 10% equity.)
          </span>
        </label>

        <label className="flex items-center">
          <input
            type="checkbox"
            checked={isChecked[1]}
            onChange={() => handleCheckboxChange(1)}
            className="h-7 w-7"
          />
          <span className="ml-10 max-w-xs">
            I confirm that I will not use this platform to sell services or
            promote my product.
          </span>
        </label>
        <label className="flex items-center">
          <input
            type="checkbox"
            checked={isChecked[2]}
            onChange={() => handleCheckboxChange(2)}
            className="h-7 w-7"
          />
          <span className="ml-10 max-w-xs">
            I confirm that I will not use this platform to try to hire
            non-founder employees for my company.
          </span>
        </label>
        <label className="flex items-center">
          <input
            type="checkbox"
            checked={isChecked[3]}
            onChange={() => handleCheckboxChange(3)}
            className="h-7 w-7"
          />
          <span className="ml-10 max-w-xs">
            I confirm that I will treat everyone I meet on the platform with
            respect, and will not act in a way that is rude or offensive.
          </span>
        </label>
        <label className="flex items-center">
          <input
            type="checkbox"
            checked={isChecked[4]}
            onChange={() => handleCheckboxChange(4)}
            className="h-7 w-7"
          />
          <span className="ml-10 max-w-xs">
            I confirm that I will not use this platform for any purpose other
            than to find a co-founder.
          </span>
        </label>
        <button
          disabled={!isAllChecked}
          className={`${p2.className} md:mr-5 px-3 py-2 bg-[#21FF7E] text-black rounded-md text-base hover:bg-[#29a35c] hover:text-white cursor-pointer disabled:bg-gray-600 disabled:cursor-not-allowed`}
          onClick={() => addNewUser()}
        >
          I agree
        </button>
      </div>
    </div>
  );
}
export default Rules;

I tried wrapping the return block in a conditional if(!existingUser) statement but it still doesn't work.

How can I delay the return function until the useEffect checks if the user already exists?


Solution

  • Posting as an answer from my comment:

    "Because the useEffect hook executes after the initial render, the form is rendered for a little period of time before the useEffect function verifies whether or not the user already exists. The existingUser state is initially set to false when the component mounts, therefore the component will render the form before the checkExistingUser function changes the value of existingUser to true or false. You can extend the program with an additional state variable. After the checkExistingUser function has been called, this state variable can be modified from false to true."