Home > OS >  How can I restrict pages to guest users using React-Router?
How can I restrict pages to guest users using React-Router?

Time:02-08

I'm using Firebase v9 and react-router v6. I haven't used v6 so this was quite confusing. How can I make it where the guest user can only access the login page. Only users who were logged in can access the homepage and other pages.

Everytime I'll reload any page, it will show this in the console but it will still direct the user to the right page :

No routes matched location "/location of the page"

How can I use a private route for the profile page?

//custom hook
export function useAuth() {
  const [currentUser, setCurrentUser] = useState();

  useEffect(() => {
    const unsub = onAuthStateChanged(auth, (user) => setCurrentUser(user));
    return unsub;
  }, []);

  return currentUser;
}

App.js

 import { auth, useAuth } from "./Firebase/utils";
    import { onAuthStateChanged } from "firebase/auth";

  function App() {
  const currentUser = useAuth();
  const user = auth.currentUser;
  const navigate = useNavigate();

  console.log(currentUser?.email);

  useEffect(() => {
    onAuthStateChanged(auth, (user) => {
      if (user) {
        // User is signed in, see docs for a list of available properties
        // https://firebase.google.com/docs/reference/js/firebase.User
        const uid = user.uid;
        console.log(uid);
        navigate("/Home");
        // ...
      } else {
        // User is signed out
        // ...
        navigate("/");
      }
    });
  }, []);

  return (
    <div>
      <div>
        <Routes>
          
          {currentUser ? (
            <>
             //If i do it this way and I'll go the profile page and reload it, it will always go to back to the Homepage.
              <Route path="/Home" element={<Home />} />
              <Route path="/Profile" element={<ProfilePage />} />
            </>
          ) : (
            <>
              <Route
                path="/"
                element={
                    <LogInPage />
                }
              />
            </>
          )}

        </Routes>
      </div>
    </div>
  );
}

export default App;

This is what the console.log(user) shows:

enter image description here

Package.json file:

enter image description here

CodePudding user response:

Issues

The main issue is that the currentUser value is initially falsey

const [currentUser, setCurrentUser] = useState();

and you are making a navigation decision on unconfirmed authentication status in App

<Routes>
  {currentUser ? (
    <>
      // If i do it this way and I'll go the profile page and reload it, 
      // it will always go to back to the Homepage.
      <Route path="/Home" element={<Home />} />
      <Route path="/Profile" element={<ProfilePage />} />
    </>
  ) : (
    <>
      <Route
        path="/"
        element={<LogInPage />}
      />
    </>
  )}
</Routes>

When refreshing the page the currentUser state is reset, is undefined, i.e. falsey, and only the "/" path is rendered.

Solution

In react-router-dom is a common practice to abstract route protection into a specialized "protected route" component. You will also want to conditionally handle the indeterminant state until your Firebase auth check has had a chance to confirm an authentication status and update the currentUser state.

Example:

export function useAuth() {
  const [currentUser, setCurrentUser] = useState({}); // <-- provide object to destructure from

  useEffect(() => {
    const unsub = onAuthStateChanged(auth, (user) => setCurrentUser(user));
    return unsub;
  }, []);

  return currentUser;
}

AuthWrapper - Uses the useAuth hook to check the authentication status of user. If currentUser is undefined it conditionally returns early null or some other loading indicator. Once the currentUser state is populated/defined the component conditionally renders either an Outlet for nested/wrapped Route components you want to protect, or the Navigate component to redirect to your auth route.

import { Navigate, Outlet, useLocation } from 'react-router-dom';

const AuthWrapper = () => {
  const location = useLocation();
  const { currentUser } = useAuth();

  if (currentUser === undefined) return null; // <-- or loading spinner, etc...

  return currentUser 
    ? <Outlet />
    : <Navigate to="/" replace state={{ from: location }} />;
};

App - Unconditionally renders all routes, wrapping the Home and Profile routes in the AuthWrapper layout route.

function App() {
  return (
    <div>
      <div>
        <Routes>
          <Route element={<AuthWrapper />}>
            <Route path="/Home" element={<Home />} />
            <Route path="/Profile" element={<ProfilePage />} />
          </Route>
          <Route path="/" element={<LogInPage />} />
        </Routes>
      </div>
    </div>
  );
}
  •  Tags:  
  • Related