I'm trying to create a MERN stack Facebook clone where if the user is not logged in, they will be taken to a landing page with signup/login forms. If the user is logged in, they will be taken to the main app with "/"
, "/messages"
, "/profile"
, and "/users/:userID"
routes.
Originally I had them all within app.js
, with a isLoggedIn
function that returns a boolean. I tried to do the following, but now it can't find any of my logged-in only routes except for "/"
. Everything works as it should on "/"
and "/welcome"
, and the re-direction works both ways in app.js
.
Index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './components/app/App';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<React.StrictMode>
<App />
</React.StrictMode>
</BrowserRouter>
);
App.js
import './App.css';
import React, { useState } from 'react';
import Feed from '../feed/Feed';
import HomePage from '../../pages/HomePage';
import {
useNavigate,
Routes,
Route,
Navigate,
} from "react-router-dom";
import { isLoggedIn } from '../../utilities/LoggedInCheck';
import LandingPage from '../../pages/LandingPage';
import AuthenticatedRoutes from './AuthenticatedRoutes';
const App = () => {
const navigate = useNavigate();
return (
<Routes>
{/* ====== AUTHENTICATION ONLY - Search, Messages, Friends, Notifications : ======== */}
<Route
path='/'
element={isLoggedIn()
? <AuthenticatedRoutes navigate={navigate} />
: <Navigate to='/welcome' />
}
/>
{/* ====== NO AUTHENTICATION - Sign Up or Login: ======== */}
<Route
path='/welcome'
element={!isLoggedIn()
? <LandingPage navigate={navigate} />
: <Navigate to='/' />
}
/>
</Routes>
);
}
export default App;
AuthenticatedRoutes.js - I use the same layout for each authenticated page, so skip to where it says "MAIN DIV" for the route portion.
import React, { useState, useEffect } from 'react';
import {
Routes,
Route,
} from "react-router-dom";
import { useSessionTimeOutCheck } from '../../utilities/LoggedInCheck';
import { isLoggedIn } from '../../utilities/LoggedInCheck';
import LoginPopup from '../auth/LoginPopup';
import { findUser } from '../../api_calls/usersAPI';
import HomePage from '../../pages/HomePage';
import ProfilePage from '../../pages/ProfilePage';
import OwnProfilePage from '../../pages/OwnProfilePage';
import MessengerPage from '../../pages/MessengerPage';
import Profile from '../profilepage/Profile';
import OwnProfile from '../profilepage/OwnProfile';
import Navbar from '../navbar/Navbar';
import getSessionUserID from '../../utilities/GetSessionUserID';
import Feed from '../feed/Feed';
const AuthenticatedRoutes = ({ navigate }) => {
const [token, setToken] = useState(window.localStorage.getItem('token'));
const sessionUserID = getSessionUserID(token);
const [sessionUser, setSessionUser] = useState(null);
// ===== LOGIN POPUP & TIMEOUT CHECKER: COPY TO EVERY AUTHENTICATED PAGE: ==========
const showLoginPopup = !useSessionTimeOutCheck(); // checks every 5 seconds if token is valid and changes true/false
// on component mount: get sessionUserInfo
// TODO test:copy to every page, so that it reloads on every new page visit?
useEffect(() => {
if (token && sessionUserID) {
findUser(token, sessionUserID)
.then(userData => {
window.localStorage.setItem("token", userData.token)
setToken(window.localStorage.getItem("token"))
setSessionUser(userData.user);
console.log(userData.user);
})
}
},[])
// =========== JSX FOR COMPONENT ===================================
return (
<div className='h-screen w-screen bg-#bgGrey dark:bg-gray-900 flex flex-col'>
{/* LOGGED OUT POPUP */}
{showLoginPopup &&
<div className='z-40 absolute h-full w-full'>
<LoginPopup navigate={navigate} />
</div>
}
{/* NAV BAR */}
<div className='z-30'>
<Navbar navigate={navigate} token={token} setToken={setToken}
sessionUserID={sessionUserID} sessionUser={sessionUser} setSessionUser={sessionUser}/>
</div>
{/* MAIN PAGE */}
<div className='w-screen h-screen flex flex-row '>
{/* MAIN DIV */}
<div className='w-full h-full'>
<Routes>
{/* ------ FEED ------ */}
<Route path='/' element={
<Feed navigate={navigate} token={token} setToken={setToken}
sessionUserID={sessionUserID} sessionUser={sessionUser} setSessionUser={sessionUser}/>} />
{/* ------ PROFILE PAGE ------ */}
<Route path="/users/:userID/" element={
<Profile navigate={navigate} token={token} setToken={setToken}
sessionUserID={sessionUserID} sessionUser={sessionUser} setSessionUser={sessionUser}/>}/>
{/* ------ SESSION USER'S PROFILE PAGE ------ */}
<Route path='/profile' element={
<OwnProfile navigate={navigate} token={token} setToken={setToken}
sessionUserID={sessionUserID} sessionUser={sessionUser} setSessionUser={sessionUser}/>}/>
{/* ------ MESSAGES ------ */}
{/* <Route path='/messages' element={
<MessengerPage navigate={navigate} token={token} setToken={setToken}
sessionUserID={sessionUserID} sessionUser={sessionUser} setSessionUser={sessionUser}/>}/> */}
</Routes>
</div>
{/* MESSENGER DIV - Online friends */}
<div className='flex flex-row items-center justify-between h-full sm:w-[28rem] md:w-[30.5rem] lg:w-[34.5rem] px-4
border-l-2'>
MESSENGER
</div>
</div>
</div>
);
}
export default AuthenticatedRoutes;
The route rendered AuthenticatedRoutes
should specify a wildcard matcher so all descendent routes can also be matched and rendered, e.g. path="/*"
. This should allows the Routes
component that AuthenticatedRoutes
renders to match sub-routes.
See Splats for more details.
const App = () => {
const navigate = useNavigate();
return (
<Routes>
{/* ====== AUTHENTICATION ONLY - Search, Messages, Friends, Notifications : ======== */}
<Route
path='/*' // <-- allow sub-route matching
element={isLoggedIn()
? <AuthenticatedRoutes navigate={navigate} />
: <Navigate to='/welcome' replace />
}
/>
{/* ====== NO AUTHENTICATION - Sign Up or Login: ======== */}
<Route
path='/welcome'
element={!isLoggedIn()
? <LandingPage navigate={navigate} />
: <Navigate to='/' replace />
}
/>
</Routes>
);
}
For a more conventional route protection implementation see my answer here. The basic gist is that AuthenticatedRoutes
would render an Outlet
for nested routes to render instead of directly rendering the descendent routes. It leads to cleaner code and separation of concerns.