React router v6 2elements with the same url path '/' - javascript

I am trying to have 2elements Dashboard and Login with the same url path ('/') but in two separated outlets when the user is logged in, i want to render the Dashboard and when is not render the Login page
import { lazy, Suspense, useMemo } from 'react'
import {
Route,
BrowserRouter,
Routes,
Navigate,
Outlet,
} from 'react-router-dom'
import * as PATHS from './constants/routes'
import UserContext from './context/user'
import useAuthListener from './hooks/use-auth-listener'
const Login = lazy(() => import('./pages/Login'))
const Register = lazy(() => import('./pages/Register'))
const NotFound = lazy(() => import('./pages/NotFound'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const PrivateOutlet = () => {
const { user } = useAuthListener()
return user ? <Outlet /> : <Navigate to={PATHS.LOGIN} />
}
const PublicOutlet = () => {
const { user } = useAuthListener()
return !user ? <Outlet /> : <Navigate to={PATHS.DASHBOARD} />
}
const App = () => {
const { user } = useAuthListener()
const value = useMemo(
() => ({
user,
}),
[{ user }]
)
return (
<UserContext.Provider value={value}>
<Suspense fallback={<p>Loading...</p>}>
<BrowserRouter>
<Routes>
<Route element={<PublicOutlet />}>
<Route path={PATHS.LOGIN} element={<Login />} />
<Route path={PATHS.SIGNUP} element={<Register />} />
</Route>
<Route element={<PrivateOutlet />}>
<Route path={PATHS.NOTFOUND} element={<NotFound />} />
<Route path={PATHS.DASHBOARD} element={<Dashboard />} />
</Route>
</Routes>
</BrowserRouter>
</Suspense>
</UserContext.Provider>
)
}
export default App
I already have a workaround of removing the PublicOutlet and adding a
<Route path={PATHS.LOGIN} element={!user ? <Login />} :<Dashboard />
but i would like to check if there is a better way of doing it in the PublicOutlet am using react router V6

Related

How to implement lazy loading and code splitting with react-router?

I am using React v17, and React Router v6+. When I load the page, the browser downloads all the js which is around 900kb, reducing the initial load time.
My routes are defined like so
const PrivateRoute = lazy(() => import("../utils/AuthenticatedRoutes"));
const Profile = lazy(() => import("../modules/Settings/User/Profile"));
const Buddies = lazy(() => import("../modules/Buddies/Buddies"));
const Buddy = lazy(() => import("../modules/Buddies/Buddy"));
const App = () => {
return (
<Suspense fallback={<Loader />}>
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/profile" element={<PrivateRoute render={<Profile />} />} />
<Route path="/buddies" element={<PrivateRoute render={<Buddy />} />} />
</Routes>
</Suspense>
)
}
This is the Private Route component
const PrivateRoute = ({ render }: { render: any }) => {
const location = useLocation();
const { loggedIn } = useSelector((state: RootState) => state.userReducer);
const pathname = location.pathname;
if (!loggedIn) {
return <Navigate to={`/login?redirectTo=${pathname}&search=${location.search}`} />;
}
return render;
};
Problem:
When I load any page on the app, even the one with no element in it, the entire JS of 999kb is downloaded and I don't think lazy loading should work that way.
How can I handle this ?
This is normal. You are wrapping the whole app with Suspense, which is directly resolved by your fallback while anything under it is suspended.
How to implement Suspense with react router ?
You should defined Suspense for each route you want to have lazy loading. So when the route is called, suspense will call the fallback while anything under it is suspended.
const PrivateRoute = lazy(() => import("../utils/AuthenticatedRoutes"));
const Profile = lazy(() => import("../modules/Settings/User/Profile"));
const Buddies = lazy(() => import("../modules/Buddies/Buddies"));
const Buddy = lazy(() => import("../modules/Buddies/Buddy"));
const App = () => {
return (
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/profile" element={<PrivateRoute render={<React.Suspense fallback={<Loader />}><Profile /></React.Suspense>} />} />
<Route path="/buddies" element={<PrivateRoute render={<React.Suspense fallback={<Loader />}><Buddy /></React.Suspense>} />} />
</Routes>
)
}
This is the same as the official documentation
I don't see any overt issues with the way you've implemented your code. The code splitting appears to be working.
Suggestion
I suggest refactoring the PrivateRoute component to be a layout route component instead of a wrapper component.
Example:
import { Navigate, Outlet, useLocation } from 'react-router-dom';
const PrivateRoute = () => {
const location = useLocation();
const { loggedIn } = useSelector((state: RootState) => state.userReducer);
const pathname = location.pathname;
if (!loggedIn) {
return <Navigate to={`/login?redirectTo=${pathname}&search=${location.search}`} />;
}
return <Outlet />;
};
...
const PrivateRoute = lazy(() => import("../utils/AuthenticatedRoutes"));
const Profile = lazy(() => import("../modules/Settings/User/Profile"));
const Buddies = lazy(() => import("../modules/Buddies/Buddies"));
const Buddy = lazy(() => import("../modules/Buddies/Buddy"));
const App = () => {
return (
<Suspense fallback={<Loader />}>
<Routes>
<Route path="/" element={<Landing />} />
<Route element={<PrivateRoute />}>
<Route path="/profile" element={<Profile />} />
<Route path="/buddies" element={<Buddy />} />
</Route>
</Routes>
</Suspense>
)
}
With this implementation I do see the Loader component "blip" on the screen momentarily for the first time each dynamically imported component is routed to.
I think it might be as Colin called out in a comment, that the dynamically imported components just aren't a significant portion of your overall app bundle size.

react router render component multiple times when refreshing

I'm trying to understand why when refreshing the page, the component is called multiple times:
MainLayout.tsx: (routes component)
import { FC, ReactElement, useEffect } from 'react'
import { Routes, Route, BrowserRouter } from 'react-router-dom'
import { createBrowserHistory } from 'history'
import { useLocation } from 'react-router-dom'
import { Navigate } from 'react-router-dom'
import { Outlet } from 'react-router-dom'
import { IntroductionPage } from '../pages/introduction/introduction-page'
import { useTranslation } from 'react-i18next'
import { storage } from '../utils/storage'
export const history = createBrowserHistory()
export const MainLayout: FC = () => {
const { t } = useTranslation()
useEffect(() => {
console.log('MainLayout:: constructor')
}, [])
const RequireAuth = (): ReactElement => {
const { token } = storage.getState().authReducer
let location = useLocation()
if (!token) return <Navigate to="/login" state={{ from: location }} />
return <Outlet />
}
return (
<BrowserRouter history={history}>
<div className="main-wrapper">
<div className="content-wrapper">
<div className="main-content">
<Routes>
<Route path="/" element={<Login />} />
<Route path="/login" element={<Login />} />
<Route element={<RequireAuth />}>
<Route path="/acceptance" element={<AcceptancePage />} />
<Route path="/introduction/:page" element={<IntroductionPage />} />
</Route>
</Routes>
</div>
</div>
</div>
</BrowserRouter>
)
}
introduction page
I put the following code in introduction page:
useEffect(() => {
console.log('IntroductionPage:: constructor')
setIntroduction(introductions[+page - 1])
}, [])
I'm refreshing the introduction page, and see in the console:
IntroductionPage:: constructor
MainLayout.tsx:23 MainLayout:: constructor
IntroductionPage:: constructor
IntroductionPage:: constructor
Appreciate any help
Ah, I see why the IntroductionPage component is mounted twice. The RequireAuth component is declared inside another React component. Since it is redeclared each render cycle it's a new React component reference so React unmounts the instance from the previous render cycle and mounts a new instance. All children it renders will also be new instances.
It should be declared out on its own, outside of any other React component.
Example:
const RequireAuth = (): ReactElement => {
const { token } = storage.getState().authReducer;
const location = useLocation();
if (!token) return <Navigate to="/login" state={{ from: location }} />;
return <Outlet />;
};
export const MainLayout: FC = () => {
const { t } = useTranslation();
useEffect(() => {
console.log('MainLayout:: constructor');
}, []);
return (
<BrowserRouter history={history}>
<div className="main-wrapper">
<div className="content-wrapper">
<div className="main-content">
<Routes>
<Route path="/" element={<Login />} />
<Route path="/login" element={<Login />} />
<Route element={<RequireAuth />}>
<Route path="/acceptance" element={<AcceptancePage />} />
<Route path="/introduction/:page" element={<IntroductionPage />} />
</Route>
</Routes>
</div>
</div>
</div>
</BrowserRouter>
);
};

Error: [PrivateRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

I'm using React Router v6 and am creating private routes for my application.
In file PrivateRoute.js, I've the code
import React from 'react';
import {Route,Navigate} from "react-router-dom";
import {isauth} from 'auth'
function PrivateRoute({ element, path }) {
const authed = isauth() // isauth() returns true or false based on localStorage
const ele = authed === true ? element : <Navigate to="/Home" />;
return <Route path={path} element={ele} />;
}
export default PrivateRoute
And in file route.js I've written as:
...
<PrivateRoute exact path="/" element={<Dashboard/>}/>
<Route exact path="/home" element={<Home/>}/>
I've gone through the same example React-router Auth Example - StackBlitz, file App.tsx
Is there something I'm missing?
I ran into the same issue today and came up with the following solution based on this very helpful article by Andrew Luca
In PrivateRoute.js:
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoute = () => {
const auth = null; // determine if authorized, from context or however you're doing it
// If authorized, return an outlet that will render child elements
// If not, return element that will navigate to login page
return auth ? <Outlet /> : <Navigate to="/login" />;
}
In App.js (I've left in some other pages as examples):
import './App.css';
import React, {Fragment} from 'react';
import {BrowserRouter as Router, Route, Routes} from 'react-router-dom';
import Navbar from './components/layout/Navbar';
import Home from './components/pages/Home';
import Register from './components/auth/Register'
import Login from './components/auth/Login';
import PrivateRoute from './components/routing/PrivateRoute';
const App = () => {
return (
<Router>
<Fragment>
<Navbar/>
<Routes>
<Route exact path='/' element={<PrivateRoute/>}>
<Route exact path='/' element={<Home/>}/>
</Route>
<Route exact path='/register' element={<Register/>}/>
<Route exact path='/login' element={<Login/>}/>
</Routes>
</Fragment>
</Router>
);
}
In the above routing, this is the private route:
<Route exact path='/' element={<PrivateRoute/>}>
<Route exact path='/' element={<Home/>}/>
</Route>
If authorization is successful, the element will show. Otherwise, it will navigate to the login page.
Only Route components can be a child of Routes. If you follow the v6 docs then you'll see the authentication pattern is to use a wrapper component to handle the authentication check and redirect.
function RequireAuth({ children }: { children: JSX.Element }) {
let auth = useAuth();
let location = useLocation();
if (!auth.user) {
// Redirect them to the /login page, but save the current location they were
// trying to go to when they were redirected. This allows us to send them
// along to that page after they login, which is a nicer user experience
// than dropping them off on the home page.
return <Navigate to="/login" state={{ from: location }} />;
}
return children;
}
...
<Route
path="/protected"
element={
<RequireAuth>
<ProtectedPage />
</RequireAuth>
}
/>
The old v5 pattern of create custom Route components no longer works. An updated v6 pattern using your code/logic could look as follows:
const PrivateRoute = ({ children }) => {
const authed = isauth() // isauth() returns true or false based on localStorage
return authed ? children : <Navigate to="/Home" />;
}
And to use
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
Complement to reduce lines of code, make it more readable and beautiful.
This could just be a comment but I don't have enough points, so I'll
put it as an answer.
Dallin's answer works but Drew's answer is better! And just to complete Drew's answer on aesthetics, I recommend creating a private component that takes components as props instead of children.
Very basic example of private routes file/component:
import { Navigate } from 'react-router-dom';
const Private = (Component) => {
const auth = false; //your logic
return auth ? <Component /> : <Navigate to="/login" />
}
Route file example:
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/user" element={<Private Component={User} />} />
</Routes>
I know that this is not exactly the recipe on how to make PirvateRoute work, but I just wanted to mention that the new documentation recommends a slightly different approach to handle this pattern with react-router v6:
<Route path="/protected" element={<RequireAuth><ProtectedPage /></RequireAuth>} />
import { Navigate, useLocation } from "react-router";
export const RequireAuth: React.FC<{ children: JSX.Element }> = ({ children }) => {
let auth = useAuth();
let location = useLocation();
if (!auth.user) {
return <Navigate to="/login" state={{ from: location }} />;
}
return children;
};
And you are supposed to add more routes inside ProtectedPage itself if you need it.
See the documentation and an example for more details. Also, check this note by Michael Jackson that goes into some implementation details.
Just set your router component to element prop:
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
You can also check for upgrading from v5.
Remove the PrivateRoute component from your project and use the following code in your App.js files:
import {Navigate} from "react-router-dom";
import {isauth} from 'auth'
...
<Route exact path="/home" element={<Home/>}/>
<Route exact path="/" element={isauth ? <Dashboard/> : <Navigate to="/Home" />}/>
It's 2022 and I did something like below:
// routes.tsx
import { lazy } from "react";
import { Routes, Route } from "react-router-dom";
import Private from "./Private";
import Public from "./Public";
const Home = lazy(() => import("../pages/Home/Home"));
const Signin = lazy(() => import("../pages/Signin/Signin"));
export const Router = () => {
return (
<Routes>
<Route path="/" element={Private(<Home />)} />
<Route path="/signin" element={Public(<Signin />)} />
</Routes>
);
};
// Private.tsx
import { Navigate } from "react-router-dom";
import { useEffect, useState } from "react";
function render(c: JSX.Element) {
return c;
}
const Private = (Component: JSX.Element) => {
const [hasSession, setHasSession] = useState<boolean>(false);
useEffect(() => {
(async function () {
const sessionStatus = await checkLoginSession();
setHasSession(Boolean(sessionStatus));
})();
}, [hasSession, Component]);
return hasSession ? render(Component) : <Navigate to="signin" />;
};
export default Private;
Hope this helps!
React Router v6, some syntactic sugar:
{auth && (
privateRoutes.map(route =>
<Route
path={route.path}
key={route.path}
element={auth.isAuthenticated ? <route.component /> : <Navigate to={ROUTE_WELCOME_PAGE} replace />}
/>
)
)}
I tried all answers, but it always displayed the error:
Error: [PrivateRoute] is not a component. All component children of must be a or <React.Fragment>
But I found a solution ))) -
In PrivateRoute.js file:
import React from "react"; import { Navigate } from "react-router-dom";
import {isauth} from 'auth'
const PrivateRoute = ({ children }) => {
const authed = isauth()
return authed ? children : <Navigate to={"/Home" /> };
export default ProtectedRoute;
In the route.js file:
<Route
path="/"
element={
<ProtectedRoute >
<Dashboard/>
</ProtectedRoute>
}
/>
<Route exact path="/home" element={<Home/>}/>
Children of Routes need to be Route elements, so we can change the ProtectedRoute:
export type ProtectedRouteProps = {
isAuth: boolean;
authPath: string;
outlet: JSX.Element;
};
export default function ProtectedRoute({
isAuth,
authPath,
outlet,
}: ProtectedRouteProps) {
if (isAuth) {
return outlet;
} else {
return <Navigate to={{pathname: authPath}} />;
}
}
And then use it like this:
const defaultProps: Omit<ProtectedRouteProps, 'outlet'> = {
isAuth: //check if user is authenticated,
authPath: '/login',
};
return (
<div>
<Routes>
<Route path="/" element={<ProtectedRoute {...defaultProps} outlet={<HomePage />} />} />
</Routes>
</div>
);
This is the simple way to create a private route:
import React from 'react'
import { Navigate } from 'react-router-dom'
import { useAuth } from '../../context/AuthContext'
export default function PrivateRoute({ children }) {
const { currentUser } = useAuth()
if (!currentUser) {
return <Navigate to='/login' />
}
return children;
}
Now if we want to add a private route to the Dashboard component we can apply this private route as below:
<Routes>
<Route exact path="/" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
</Routes>
For longer elements
<Router>
<div>
<Navbar totalItems={cart.total_items}/>
<Routes>
<Route exact path='/'>
<Route exact path='/' element={<Products products={products} onAddToCart={handleAddToCart}/>}/>
</Route>
<Route exact path='/cart'>
<Route exact path='/cart' element={<Cart cart={cart}/>}/>
</Route>
</Routes>
</div>
</Router>
Header will stay on all page
import React from 'react';
import {
BrowserRouter,
Routes,
Route
} from "react-router-dom";
const Header = () => <h2>Header</h2>
const Dashboard = () => <h2>Dashboard</h2>
const SurveyNew = () => <h2>SurveyNew</h2>
const Landing = () => <h2>Landing</h2>
const App = () =>{
return (
<div>
<BrowserRouter>
<Header />
<Routes >
<Route exact path="/" element={<Landing />} />
<Route path="/surveys" element={<Dashboard />} />
<Route path="/surveys/new" element={<SurveyNew/>} />
</Routes>
</BrowserRouter>
</div>
);
};
export default App;
<Route path='/' element={<Navigate to="/search" />} />
You can use a function for a private route:
<Route exact path="/login" element={NotAuth(Login)} />
<Route exact path="/Register" element={NotAuth(Register)} />
function NotAuth(Component) {
if (isAuth)
return <Navigate to="/" />;
return <Component />;
}
I'm using "react-router-dom": "^6.3.0" and this is how I did mine
PrivateRoute Component and Route
import {Route} from "react-router-dom";
const PrivateRoute = ({ component: Compontent, authenticated }) => {
return authenticated ? <Compontent /> : <Navigate to="/" />;
}
<Route
path="/user/profile"
element={<PrivateRoute authenticated={true} component={Profile} />} />
For the error "[Navigate] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>", use the following method maybe solved:
DefaultPage is when no match router. Jump to the DefaultPage. Here use the <Route index element={} /> to replace the
<Navigate to={window.location.pathname + '/kanban'}/>
See Index Routes
<Routes>
<Route path={'/default'} element={<DefaultPage/>}/>
<Route path={'/second'} element={<SecondPage/>}/>
{/* <Navigate to={window.location.pathname + '/kanban'}/> */}
<Route index element={<DefaultPage/>} />
</Routes>
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<h1>home page</h1>} />
<Route path="/seacrch" element={<h1>seacrch page</h1>} />
</Routes>
</Router>
);
}
export default App;

Always redirected to current route instead of desired route

I am using ternary operator to render component using react route. But I am always redirected to "/" route instead of desired component even if the condition is as expected.
I get desired functionality only when I refresh/reload the page.
This is my App.js with all the routes
import React, { useEffect, useState } from 'react';
import Header from './Components/Header';
import Home from './Components/Home';
import Checkout from "./Components/Checkout";
import Payment from "./Components/Payment";
import NewProduct from "./Components/NewProduct";
import OrderSuccess from "./Components/OrderSuccess";
import AddressForm from './Components/AddressForm';
import {BrowserRouter, Switch, Route} from "react-router-dom";
import { Redirect } from 'react-router';
import Login from './Components/Login';
import Orders from "./Components/Orders";
import Account from './Components/Account';
import { connect } from 'react-redux';
const App=()=>{
const user = (JSON.parse(localStorage.getItem("profile")));
const defaultRoutes= ()=>{
return(
<div>
<Header/>
<Switch>
<Route path="/account-settings" exact component={()=>user ? <Account />: <Redirect to="/" />} />
<Route path="/orders" exact component={()=>user ? <Orders /> : <Redirect to="/" />} />
<Route path="/checkout" exact component={()=>user ? <Checkout />: <Redirect to="/" />} />
<Route path="/payment" exact component={()=>user ? <Payment /> : <Redirect to="/" />} />
<Route path="/account-settings/add-new-address" exact component={()=>user?.result ? <AddressForm /> : <Redirect to="/" />} />
<Route path="/" exact component={Home} />
</Switch>
</div>
)
}
return (
<BrowserRouter>
<Switch>
<Route path="/login" exact component={()=>user ? <Redirect to="/" />:<Login />} />
<Route component={defaultRoutes} />
</Switch>
</BrowserRouter>
);
}
export default App;
This is my index.js file
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import "./index.css"
import reducers from "./reducers/index";
const store = createStore(reducers,{},compose(applyMiddleware(thunk)));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById("root")
)
This is reducer where I am updating my localstorage
export default (state={authData:null},action)=>{
switch(action.type){
case "AUTH":
localStorage.setItem("profile",JSON.stringify({...action?.data}));
return {...state, authData: action.data, loading: false, errors: null};
case "LOGOUT":
localStorage.clear();
return {...state,authData:null};
default:
return "";
}
}
Remove the ternary in the router. Use useHistory to redirect to "/" if the condition is not met.
For checking the condition, create a custom hook called useCheckUser which checks if there is a user in the localStorage.
In every component where user is required,
import useCheckUser from "file-address"
import { useHistory } from "react-router"
const Component = () => {
let history = useHistory();
let user = useCheckUser();
if (!user) {
history.push("/");
}
return ()
}
The useCheckUser hook should be something similar to
const useCheckUser = () => {
let user = localStorage.getItem("profile");
if (user) {
return JSON.parse(user);
}
return null;
};
export default useCheckUser;
NOTE: If your only requirement is rendering components when the user is present, you can change your custom hook so that it does everything including the redirecting. This is just to return the current user. So this can be used if you want to render certain components like auth pages if and only if the user is not present.
try to take the user a State (useState), and set it in the useEffect like this:
const [user, setUser] = useState(null);
useEffect(() => {
setUser(localStorage.getItem("profile"))
}, [])
it will set the "user" as your page rendered
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
setUser(localStorage.getItem("profile"))
}, [])
const authentication = {
getLogInStatus() {
return user;
},
};
}
function SecuredRoute(props) {
return (
<Route
path={props.path}
render={(data) =>
authentication.getLogInStatus() ? (
<props.component
{...data}
></props.component>
) : (
handleRedirect()
)
}
></Route>
);
}
const handleRedirect = () => {
let login = window.confirm(
"please Register or log in to access this page!"
);
if (login) {
return <Redirect path="/"></Redirect>;
}
};
return (
<BrowserRouter>
<Switch>
// those routes which don't needs authetication
<Route path="/home" compponent={Home} />
<Route path="/login" compponent={Login} />
//and others
// those routes which needs authetication
<SecuredRoute path="/bookings" component={Bookings} />
<SecuredRoute
path="/booking-details"
component={BookingDetails}
/>
<SecuredRoute path="/favorites" component={Favorites} />
<SecuredRoute path="/profile" component={Profile} />
</Switch>
</BrowserRouter>
);
}
Issue
You are close. The issue here is that even though you store the auth in localStorage and update the redux state, App isn't being rerendered to "pick up" the new auth value from localStorage.
Solution
Initialize the redux state from localStorage.
Select the authentication state from your Redux store. This is so changes to the store trigger subscribed components to rerender.
Auth Reducer
const initialState={
authData:JSON.parse(localStorage.getItem("profile")),
loading:false,
errors:null
};
export default (state = initialState, action) => {
switch(action.type){
case "AUTH":
localStorage.setItem("profile", JSON.stringify({ ...action?.data }));
return {
...state,
authData: action?.data,
loading: false,
errors: null
};
case "LOGOUT":
localStorage.clear();
return { ...state, authData: null };
default:
return state;
}
};
App
import { useSelector } from "react-redux";
const App = () => {
const user = useSelector(state => state.auth.authData);
const defaultRoutes = () => {
return (
<div>
<Header/>
<Switch>
<Route
path="/account-settings"
render={() => user ? <Account />: <Redirect to="/" />}
/>
<Route
path="/orders"
render={() => user ? <Orders /> : <Redirect to="/" />}
/>
<Route
path="/checkout"
render={() => user ? <Checkout />: <Redirect to="/" />}
/>
<Route
path="/payment"
render={() => user ? <Payment /> : <Redirect to="/" />}
/>
<Route
path="/account-settings/add-new-address"
render={() => user?.result ? <AddressForm /> : <Redirect to="/" />}
/>
<Route path="/" component={Home} />
</Switch>
</div>
)
};
return (
<BrowserRouter>
...
</BrowserRouter>
);
}
Suggestion
Review the auth workflow and create a PrivateRoute component that handles the redirect for you.
Example:
const PrivateRoute = props => {
const user = useSelector(state => state.auth.authData);
return user ? <Route {...props} /> : <Redirect to="/" />
};
Usage:
const App = () => {
const defaultRoutes = () => {
return (
<div>
<Header/>
<Switch>
<PrivateRoute path="/account-settings" component={Account} />
<PrivateRoute path="/orders" component={Orders} />
<PrivateRoute path="/checkout" component={Checkout} />
<PrivateRoute path="/payment" component={Payment} />
<Route
path="/account-settings/add-new-address"
render={() => user?.result ? <AddressForm /> : <Redirect to="/" />}
/>
<Route path="/" component={Home} />
</Switch>
</div>
)
};
return (
<BrowserRouter>
...
</BrowserRouter>
);
}

Always getting redirected to homepage on refresh from any route in app

I am using functional component which provides authentication to the specific routes such as /home using server side authentication and local storage, happening in OnSubmit event of my of login page.
Everything works fine I am getting redirected to homepage after successful login, the problem arises when I refresh the page from any route I am always getting redirected to home page, why is that so?
The problem arises in production as well as in development, anyone please suggest me a way to fix this
My App.js file is
App.js
import React, { useState, useEffect } from 'react';
import Swal from 'sweetalert2';
import {
BrowserRouter as Router,
Route,
Switch,
Redirect,
} from 'react-router-dom';
import LoginPage from './login/LoginPage';
import HomePage from './home/HomePage';
import Courses from './courses/Courses';
import Profile from './profile/Profile';
import Notes from './notes/Notes';
import Contact from './contact/Contact';
import Students from './students/Students';
import OnlineClass from './onlineClass/OnlineClass';
import NotFound from './notfound/NotFound';
import UpdateClass from './updateData/UpdateClass';
import UpdateNotes from './updateData/UpdateNotes';
const App = () => {
const [loggedIn, setLoggedIn] = useState(false);
useEffect(() => {
const loggedUser = localStorage.getItem('user');
if (loggedUser) {
setLoggedIn(true);
}
}, []);
const handleLoggedIn = () => {
setLoggedIn(true);
};
const handleLogout = () => {
Swal.fire({
title: 'Are you sure?',
showDenyButton: true,
showCancelButton: true,
confirmButtonText: `Yes`,
denyButtonText: `No`,
}).then((result) => {
if (result.isConfirmed) {
setLoggedIn(false);
localStorage.clear();
} else if (result.isDenied) {
Swal.fire('Welcome back!', '', 'info');
}
});
};
return (
<div>
<Router>
<Switch>
<Route exact strict path='/'>
{!loggedIn ? (
<LoginPage handleLoggedIn={handleLoggedIn} />
) : (
<Redirect to='/home' />
)}
</Route>
<Route exact path='/home'>
<HomePage handleLogout={handleLogout} loggedIn={loggedIn} />
</Route>
<Route exact path='/profile'>
<Profile loggedIn={loggedIn} handleLogout={handleLogout} />
</Route>
<Route exact path='/courses'>
<Courses loggedIn={loggedIn} handleLogout={handleLogout} />
</Route>
<Route exact path='/notes'>
<Notes loggedIn={loggedIn} handleLogout={handleLogout} />
</Route>
<Route exact path='/contact'>
<Contact loggedIn={loggedIn} handleLogout={handleLogout} />
</Route>
<Route exact path='/students'>
<Students loggedIn={loggedIn} handleLogout={handleLogout} />
</Route>
<Route exact path='/class'>
<OnlineClass loggedIn={loggedIn} handleLogout={handleLogout} />
</Route>
<Route exact path='/class/update/:id'>
{!loggedIn ? (
<LoginPage handleLoggedIn={handleLoggedIn} />
) : (
<UpdateClass />
)}
</Route>
<Route exact path='/notes/update/:id'>
{!loggedIn ? (
<LoginPage handleLoggedIn={handleLoggedIn} />
) : (
<UpdateNotes />
)}
</Route>
<Route>
<NotFound />
</Route>
</Switch>
</Router>
</div>
);
};
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The problem is that setLoggedIn(true); is executed after the redirect action:
You need to check if user has been loaded or not. You can initialize loggedIn to null
const [loggedIn, setLoggedIn] = useState(null);
useEffect(() => {
const loggedUser = localStorage.getItem('user');
setLoggedIn(Boolean(loggedUser));
}, []);
Then you can ignore render the routes if loggedIn === null
return (
<div>
{ loggedIn !== null &&
<Router>
...
</Router>
}
</div>
);

Categories

Resources