I’m a beginner developer 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
.
Thanks in advance!
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;
2
Answers
To resolve the issue, make changes in App.js and AuthenticatedRoutes.js to handle the routing and authentication properly.
In App.js:
and In AuthenticatedRoutes.js :
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 theRoutes
component thatAuthenticatedRoutes
renders to match sub-routes.See Splats for more details.
For a more conventional route protection implementation see my answer here. The basic gist is that
AuthenticatedRoutes
would render anOutlet
for nested routes to render instead of directly rendering the descendent routes. It leads to cleaner code and separation of concerns.