Merlin
Merlin

Reputation: 978

This React Private Route isn't catching Firebase Auth in time?

I'm working with React to build a admin panel for a website, and using Firebase as the authentication system (and data storage, etc).

I've gone through a few Private Route versions, but finally settled on the one that seems to work best with Firebase. However, there is a minor problem. It works well when the user logins in, and according to Firebase Auth documentation, by default, it should be caching the user.

However, if I log in, and then close the tab and re-open it in a new tab, I get ejected back to the login page (as it should if the user isn't logged in).

I am running the site on localhost via node, but that probably shouldn't matter. A console.log reports that the user is actually logged in, but then gets kicked back anyway. Everything is encapsulated in a useEffect, which watches the LoggedIn value, and checks the Auth State.

Is there any way to prevent this from kicking a logged-in user out when they re-open the tab?

  import { FunctionComponent, useState, useEffect } from 'react';
  import { Route, Redirect } from 'react-router-dom';
  import { getAuth, onAuthStateChanged } from 'firebase/auth';
  import Routes from '../../helpers/constants';

  export const PrivateRoute: FunctionComponent<any> = ({  
      component: Component,  
      ...rest  
      }) => {  
      const [LoggedIn, SetLoggedIn] = useState(false);

  // Check if the User is still authenticated first //
  useEffect(() => {
    const auth = getAuth();
    onAuthStateChanged(auth, (user:any) => {
      if (user.uid !== null)
      {
        SetLoggedIn(true);
        // This gets reached after the tab is re-opened, indicating it's cached.
        console.log("Logged In");
      }
    });
  }, [LoggedIn]);
  // On tab reload however, this acts as if LoggedIn is set to false after the cache check
  return (  
    <Route  
      {...rest}  
      render={(props:any) => {
          console.log(LoggedIn);
          return LoggedIn ? (  
              <Component {...props} />  
          ) : (  
              <Redirect to={Routes.LOGIN} />  
          );  
      }}  
  />  
  );  
};

Upvotes: 1

Views: 571

Answers (1)

diedu
diedu

Reputation: 20775

It redirects because in the first render of your private route the code that sets the LoggedIn state to true hasn't been executed yet. You could use an extra boolean state to avoid rendering the Routes when you haven't checked the auth status.

  ...
  const [LoggedIn, SetLoggedIn] = useState(false);
  const [loading, setLoading] = useState(true);
  ...
      if (user.uid !== null) {
        setLoading(false);
        SetLoggedIn(true);
      }
  ...
  // On tab reload however, this acts as if LoggedIn is set to false after the cache check
  if(loading) return <div>Loading...</div>; // or whatever UI you use to show a loader

  return (
    <Route
      ...
    />
  );
};

You'll need to check for the user only on the component mount, you can have an empty dependency array in the useEffect hook, and also stop listening for updates in the hook clean up

  useEffect(() => {
    const auth = getAuth();
    const unsubscribe = onAuthStateChanged(auth, (user:any) => {
      ...
    });
    return unsubscribe; // stop listening when unmount
  }, []);

But you'll be reinventing the wheel a little, there is already a hook you could use https://github.com/CSFrequency/react-firebase-hooks/tree/master/auth#useauthstate

Upvotes: 1

Related Questions