skip to Main Content

I have a ReactJs component, Navbar.jsx, and I want to either render a login and sign up button if the user is not signed in, or a Link with the user’s username if the user is signed in.

I have first enabled email and password authentication, so that I can easily use Firebase’s auth api to authenticate the user.

Then, I have a Firestore database that has a collection called users, which has all of the attributes of the users, along with the user’s email, so that I can link the users from authentication to the Firestore db with all of the properties.

The issue I am having, is that when I query the firestore database, it queries every time I go to a new page, which means that it will perform the same query every time the Navbar is re-rendered, which it is for the entire website on every page.

How can I go about only querying this once and it only changing once the user has logged out?

Currently, inside my navbar, I am first using useAuthState to determine if the user is logged in, and if they are, I have access to their email.

Then, I query the database with the user email, using an async function, then I user another async function inside my Navbar to get the data.

Then I render either "Loading…", or the user’s username inside the Navbar.

Again, the issue that I am having is that the "Loading…" displays every time I visit a new page, because it re-queries the Firestore db on re-render.

How can I fix this?

Navbar.jsx (relevant code):

const auth = getAuth();
  const [user, loading] = useAuthState(auth);
  const [userInfoLoading, setUserInfoLoading] = useState(false);
  const [userInfo, setUserInfo] = useState({});

  // Get user info
  async function fetchUserData() {
    // Indicate that we are getting the info
    setUserInfoLoading(true);
    const res = await getUserDocFromEmail('[email protected]');
    setUserInfo(res);
    setUserInfoLoading(false);
  }
  useEffect(() => {
    fetchUserData();
  }, []);
return (
        <div className={styles.authBtns}>
          {!loading && !user ? (
            <>
              <Link to='/login' className={styles.navbtn}>
                Log in
              </Link>
              <Link to='/signup' className={styles.navbtn}>
                Sign up
              </Link>
            </>
          ) : (
            <Link to='/profile' className={styles.welcomeLink}>
              {!loading && user && !userInfoLoading && userInfo
                ? `${userInfo.Username}`
                : 'Loading...'}
            </Link>
          )}
        </div>
)

firebaseQuery.js:

const db = getFirestore(app);

async function getUserDocFromEmail(userEmail) {
  const usersRef = collection(db, 'users');
  const q = query(usersRef, where('Email', '==', userEmail));
  const querySnapshot = await getDocs(q);
  let returnVal = null;
  querySnapshot.forEach((doc) => {
    // doc.data() is never undefined for query doc snapshots
    console.log(doc.data());
    returnVal = doc.data();
  });
  return returnVal;
}

export { getUserDocFromEmail };

2

Answers


  1. You can use SWR or similar library.

    If you use SWR, then you can use the useSWR hook which has caching/deduping built it. You can control the deduping interval or whether to use stale data while re-fetching. However, useSWR cache will be invalidated if you refresh the page (so frontend routing is assumed to effective caching).

    example code

    const { data, isLoading } = useSWR(
      hasRequiredValues
        ? { param1toFetcher, param2toFetcher }
        : null, // if null is passed, call to fetcher is skipped
      yourAsyncFetcher,
      {
        revalidateOnMount: true,
        keepPreviousData: true,
        revalidateOnFocus: false,
        revalidateOnReconnect: false,
        dedupingInterval: RESTORE_DEDUPING_INTERVAL_IN_MS,
      }
    );
    
    Login or Signup to reply.
  2. You can wrap your whole app in React Context which holds value whether user is loggedin or not as state so you wont need to ask to firebase auth every time, additionally you can use onAuthStateReady() function from firebase/auth which returns promise that will resolve once auth is initialised so you can set value in context like this

    const auth = getAuth(app);
    const [user, setUser] = useState(undefined);
    auth.onAuthStateReady().then(() => setUser(auth.currentUser));
    

    awaiting onAuthStateReady will ensure that auth is ready and will only resolve to either null (no login) or user(login)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search