React Router rendering blank pages with React Redux - javascript

In my App.js, I have the following:
const Index = asyncRoute(() => import('~/pages/index'))
const Register = asyncRoute(() => import('~/pages/register'))
const AddDesign = asyncRoute(() => import('~/pages/add-design'))
const Login = asyncRoute(() => import('~/pages/login'))
class App extends Component {
render() {
const { isLoggedIn } = this.props;
if(!isLoggedIn){
return (
<Switch>
<Route path={'/login'} component={Login} />
<Route path={'/register'} component={Register} />
<Redirect to={'/login'} />
</Switch>
);
}
return (
<Switch>
<Route exact path='/' component={Index}/>
<Route exact path='/add-design' component={AddDesign}/>
<Route exact path="/login" render={() => <Redirect to="/"/>} />
<Route exact path="/register" render={() => <Redirect to="/"/>} />
<Redirect to={'/'} />
</Switch>
);
}
}
function mapStateToProps({ user }) {
return {
isLoggedIn: !!user.token,
};
}
export default connect(mapStateToProps)(App);
When the user logs in, isLoggedIn is set to true and it then attempts to redirect the user back to "/"
This happens, however the page loaded is the index.html file within public, rather than the Index component.
I'm not sure if its making a difference, but my asyncRoute is a workaround for FOUC:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Loading from '~/components/Loading'
class AsyncImport extends Component {
static propTypes = {
load: PropTypes.func.isRequired,
children: PropTypes.node.isRequired
}
state = {
component: null
}
_hasClass(target, className) {
return new RegExp('(\\s|^)' + className + '(\\s|$)').test(target.className);
}
toggleFoucClass () {
const root = document.getElementById('root')
if (this._hasClass(root, 'fouc')) {
root.classList.remove('fouc')
} else {
root.classList.add('fouc');
}
}
componentWillMount () {
this.toggleFoucClass()
}
componentDidMount () {
this.props.load()
.then((component) => {
setTimeout(() => this.toggleFoucClass(), 1)
this.setState(() => ({
component: component.default
}))
})
}
render () {
return this.props.children(this.state.component)
}
}
const asyncRoute = (importFunc) =>
(props) => (
<AsyncImport load={importFunc}>
{(Component) => {
return Component === null
? <Loading size="large" />
: <Component {...props} />
}}
</AsyncImport>
)
export default asyncRoute
Can anyone explain why my users are being routed but the component not rendering?

Assuming using RR4
Try:
<Route path="/" component={App}>
<IndexRedirect to="/index" />
<Route path="index" component={Index} />
Refer to the following to get your usecase correct:
https://github.com/ReactTraining/react-router/blob/5e69b23a369b7dbcb9afc6cdca9bf2dcf07ad432/docs/guides/IndexRoutes.md

Related

When typing the route manually on the browser's search bar, Protected route is not working in react app [duplicate]

How to create a protected route with react-router-dom and storing the response in localStorage, so that when a user tries to open next time they can view their details again. After login, they should redirect to the dashboard page.
All functionality is added in ContextApi.
Codesandbox link : Code
I tried but was not able to achieve it
Route Page
import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";
function Routes() {
const { authLogin } = useContext(globalC);
console.log("authLogin", authLogin);
return (
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
export default Routes;
Context Page
import React, { Component, createContext } from "react";
import axios from "axios";
export const globalC = createContext();
export class Gprov extends Component {
state = {
authLogin: null,
authLoginerror: null
};
componentDidMount() {
var localData = JSON.parse(localStorage.getItem("loginDetail"));
if (localData) {
this.setState({
authLogin: localData
});
}
}
loginData = async () => {
let payload = {
token: "ctz43XoULrgv_0p1pvq7tA",
data: {
name: "nameFirst",
email: "internetEmail",
phone: "phoneHome",
_repeat: 300
}
};
await axios
.post(`https://app.fakejson.com/q`, payload)
.then((res) => {
if (res.status === 200) {
this.setState({
authLogin: res.data
});
localStorage.setItem("loginDetail", JSON.stringify(res.data));
}
})
.catch((err) =>
this.setState({
authLoginerror: err
})
);
};
render() {
// console.log(localStorage.getItem("loginDetail"));
return (
<globalC.Provider
value={{
...this.state,
loginData: this.loginData
}}
>
{this.props.children}
</globalC.Provider>
);
}
}
Issue
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
The Switch doesn't handle rendering anything other than Route and Redirect components. If you want to "nest" like this then you need to wrap each in generic routes, but that is completely unnecessary.
Your login component also doesn't handle redirecting back to any "home" page or private routes that were originally being accessed.
Solution
react-router-dom v5
Create a PrivateRoute component that consumes your auth context.
const PrivateRoute = (props) => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin ? (
<Route {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
);
};
Update your Login component to handle redirecting back to the original route being accessed.
export default function Login() {
const location = useLocation();
const history = useHistory();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
history.replace(from);
}
}, [authLogin, history, location]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
Render all your routes in a "flat list"
function Routes() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/dashboard" component={Dashboard} />
<PrivateRoute path="/About" component={About} />
<Route path="/login" component={Login} />
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
react-router-dom v6
In version 6 custom route components have fallen out of favor, the preferred method is to use an auth layout component.
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoutes = () => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />;
}
...
<BrowserRouter>
<Routes>
<Route path="/" element={<PrivateRoutes />} >
<Route path="dashboard" element={<Dashboard />} />
<Route path="about" element={<About />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
or
const routes = [
{
path: "/",
element: <PrivateRoutes />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "*",
element: <PageNotFound />
},
];
...
export default function Login() {
const location = useLocation();
const navigate = useNavigate();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
}
}, [authLogin, location, navigate]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
For v6:
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/protected"
element={
<RequireAuth redirectTo="/login">
<ProtectedPage />
</RequireAuth>
}
/>
</Routes>
);
}
function RequireAuth({ children, redirectTo }) {
let isAuthenticated = getAuth();
return isAuthenticated ? children : <Navigate to={redirectTo} />;
}
Link to docs:
https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f
import { v4 as uuidv4 } from "uuid";
const routes = [
{
id: uuidv4(),
isProtected: false,
exact: true,
path: "/home",
component: param => <Overview {...param} />,
},
{
id: uuidv4(),
isProtected: true,
exact: true,
path: "/protected",
component: param => <Overview {...param} />,
allowed: [...advanceProducts], // subscription
},
{
// if you conditional based rendering for same path
id: uuidv4(),
isProtected: true,
exact: true,
path: "/",
component: null,
conditionalComponent: true,
allowed: {
[subscription1]: param => <Overview {...param} />,
[subscription2]: param => <Customers {...param} />,
},
},
]
// Navigation Component
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";
// ...component logic
<Switch>
{routes.map(params => {
return (
<ProtectedRoutes
exact
routeParams={params}
key={params.path}
path={params.path}
/>
);
})}
<Route
render={() => {
props.setHideNav(true);
setHideHeader(true);
return <ErrorPage type={404} />;
}}
/>
</Switch>
// ProtectedRoute component
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";
const ProtectedRoutes = props => {
const { routeParams } = props;
const currentSubscription = 'xyz'; // your current subscription;
if (routeParams.conditionalComponent) {
return (
<Route
key={routeParams.path}
path={routeParams.path}
render={routeParams.allowed[currentSubscription]}
/>
);
}
if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
if (!routeParams.isProtected) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
return null;
};
export default ProtectedRoutes;
Would like to add highlight never forget to give path as prop to ProtectedRoute, else it will not work.
Here is an easy react-router v6 protected route. I have put all the routes I want to protect in a routes.js:-
const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]
To render the routes just map them as follows: -
<Routes>
{routes.map((routes, id) => {
return(
<Route
key={id}
path={route.path}
exact={route.exact}
name={route.name}
element={
localStorage.getItem("token") ? (
route.element
) : (
<Navigate to="/login" />
)
}
)
})
}
</Routes>
If you want an easy way to implement then use Login in App.js, if user is loggedin then set user variable. If user variable is set then start those route else it will stuck at login page. I implemented this in my project.
return (
<div>
<Notification notification={notification} type={notificationType} />
{
user === null &&
<LoginForm startLogin={handleLogin} />
}
{
user !== null &&
<NavBar user={user} setUser={setUser} />
}
{
user !== null &&
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/adduser" element={<AddUser />} /> />
<Route exact path="/viewuser/:id" element={<ViewUser />} />
</Routes>
</Router>
}
</div>
)

Protected Routes and Shared Component in React Router [duplicate]

How to create a protected route with react-router-dom and storing the response in localStorage, so that when a user tries to open next time they can view their details again. After login, they should redirect to the dashboard page.
All functionality is added in ContextApi.
Codesandbox link : Code
I tried but was not able to achieve it
Route Page
import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";
function Routes() {
const { authLogin } = useContext(globalC);
console.log("authLogin", authLogin);
return (
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
export default Routes;
Context Page
import React, { Component, createContext } from "react";
import axios from "axios";
export const globalC = createContext();
export class Gprov extends Component {
state = {
authLogin: null,
authLoginerror: null
};
componentDidMount() {
var localData = JSON.parse(localStorage.getItem("loginDetail"));
if (localData) {
this.setState({
authLogin: localData
});
}
}
loginData = async () => {
let payload = {
token: "ctz43XoULrgv_0p1pvq7tA",
data: {
name: "nameFirst",
email: "internetEmail",
phone: "phoneHome",
_repeat: 300
}
};
await axios
.post(`https://app.fakejson.com/q`, payload)
.then((res) => {
if (res.status === 200) {
this.setState({
authLogin: res.data
});
localStorage.setItem("loginDetail", JSON.stringify(res.data));
}
})
.catch((err) =>
this.setState({
authLoginerror: err
})
);
};
render() {
// console.log(localStorage.getItem("loginDetail"));
return (
<globalC.Provider
value={{
...this.state,
loginData: this.loginData
}}
>
{this.props.children}
</globalC.Provider>
);
}
}
Issue
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
The Switch doesn't handle rendering anything other than Route and Redirect components. If you want to "nest" like this then you need to wrap each in generic routes, but that is completely unnecessary.
Your login component also doesn't handle redirecting back to any "home" page or private routes that were originally being accessed.
Solution
react-router-dom v5
Create a PrivateRoute component that consumes your auth context.
const PrivateRoute = (props) => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin ? (
<Route {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
);
};
Update your Login component to handle redirecting back to the original route being accessed.
export default function Login() {
const location = useLocation();
const history = useHistory();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
history.replace(from);
}
}, [authLogin, history, location]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
Render all your routes in a "flat list"
function Routes() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/dashboard" component={Dashboard} />
<PrivateRoute path="/About" component={About} />
<Route path="/login" component={Login} />
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
react-router-dom v6
In version 6 custom route components have fallen out of favor, the preferred method is to use an auth layout component.
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoutes = () => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />;
}
...
<BrowserRouter>
<Routes>
<Route path="/" element={<PrivateRoutes />} >
<Route path="dashboard" element={<Dashboard />} />
<Route path="about" element={<About />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
or
const routes = [
{
path: "/",
element: <PrivateRoutes />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "*",
element: <PageNotFound />
},
];
...
export default function Login() {
const location = useLocation();
const navigate = useNavigate();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
}
}, [authLogin, location, navigate]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
For v6:
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/protected"
element={
<RequireAuth redirectTo="/login">
<ProtectedPage />
</RequireAuth>
}
/>
</Routes>
);
}
function RequireAuth({ children, redirectTo }) {
let isAuthenticated = getAuth();
return isAuthenticated ? children : <Navigate to={redirectTo} />;
}
Link to docs:
https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f
import { v4 as uuidv4 } from "uuid";
const routes = [
{
id: uuidv4(),
isProtected: false,
exact: true,
path: "/home",
component: param => <Overview {...param} />,
},
{
id: uuidv4(),
isProtected: true,
exact: true,
path: "/protected",
component: param => <Overview {...param} />,
allowed: [...advanceProducts], // subscription
},
{
// if you conditional based rendering for same path
id: uuidv4(),
isProtected: true,
exact: true,
path: "/",
component: null,
conditionalComponent: true,
allowed: {
[subscription1]: param => <Overview {...param} />,
[subscription2]: param => <Customers {...param} />,
},
},
]
// Navigation Component
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";
// ...component logic
<Switch>
{routes.map(params => {
return (
<ProtectedRoutes
exact
routeParams={params}
key={params.path}
path={params.path}
/>
);
})}
<Route
render={() => {
props.setHideNav(true);
setHideHeader(true);
return <ErrorPage type={404} />;
}}
/>
</Switch>
// ProtectedRoute component
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";
const ProtectedRoutes = props => {
const { routeParams } = props;
const currentSubscription = 'xyz'; // your current subscription;
if (routeParams.conditionalComponent) {
return (
<Route
key={routeParams.path}
path={routeParams.path}
render={routeParams.allowed[currentSubscription]}
/>
);
}
if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
if (!routeParams.isProtected) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
return null;
};
export default ProtectedRoutes;
Would like to add highlight never forget to give path as prop to ProtectedRoute, else it will not work.
Here is an easy react-router v6 protected route. I have put all the routes I want to protect in a routes.js:-
const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]
To render the routes just map them as follows: -
<Routes>
{routes.map((routes, id) => {
return(
<Route
key={id}
path={route.path}
exact={route.exact}
name={route.name}
element={
localStorage.getItem("token") ? (
route.element
) : (
<Navigate to="/login" />
)
}
)
})
}
</Routes>
If you want an easy way to implement then use Login in App.js, if user is loggedin then set user variable. If user variable is set then start those route else it will stuck at login page. I implemented this in my project.
return (
<div>
<Notification notification={notification} type={notificationType} />
{
user === null &&
<LoginForm startLogin={handleLogin} />
}
{
user !== null &&
<NavBar user={user} setUser={setUser} />
}
{
user !== null &&
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/adduser" element={<AddUser />} /> />
<Route exact path="/viewuser/:id" element={<ViewUser />} />
</Routes>
</Router>
}
</div>
)

React Router doesn't access Protected Route

In my typescript react project i have created a protected route to check for authenticationbefore rendering a component.
export const ProtectedRoute: React.FC<ProtectedRouteProps> = props =>{
const currentLocation = useLocation();
let redirectpath = props.redirectPathOnAuthentication;
console.log(redirectpath)
console.log('in protected route')
if(!props.isAuthenticated){
props.setRedirectPathOnAuthentication(currentLocation.pathname);
redirectpath = props.authenticationPath;
}
if(redirectpath !== currentLocation.pathname){
const renderComponent = () => <Redirect to={{pathname: redirectpath}} />;
return <Route {...props} component={renderComponent} render={undefined} />
}else{
return <Route {...props} />
}
}
I pass in the props to make conditional rendering based, on rather the user is authenticated or not.
I have the tries to access a ProtectedRoute, the user will be redirected to the route where login is possible ( named as /home route), and then redirected back to the original route.
export const RoutingHandler: React.FC = (props: Props) => {
const location = useLocation()
const [sessionContext, updateSessionContext] = useSessionContext()
console.log(location)
const setRedirectPathOnAuthentication = (path: string) =>{
updateSessionContext({...sessionContext, redirectPathOnAuthentication: path})
}
const defaultProtectedRouteProps: ProtectedRouteProps = {
isAuthenticated: !!sessionContext.isAuthenticated,
authenticationPath: '/home',
redirectPathOnAuthentication: sessionContext.redirectPathOnAuthentication || '',
setRedirectPathOnAuthentication
}
console.log(defaultProtectedRouteProps)
return (
<div>
<Navbar />
<Switch>
<ProtectedRoute {...defaultProtectedRouteProps} path="/dashboard" component={Dashboard} />
<Route exact path="/home" component={Home} />
<Route path="/about" component={About} />
<Route path="/help" component={Help} />
<Redirect from="/" to="/home" />
</Switch>
</div>
)
}
Whenever I do `history.push('/dashboard');` the `Dashboard` component is never rendered.
For desired implementation, you can try this approach, that works fine:
export const PrivateRoute = ({component: Component, isAuthenticated, ...rest}) => (
<Route {...rest}
render={
props => (
isAuthenticated ? (
<Component {...props} />
):
(
<Redirect to={{ pathname: '/login', state: { from: props.location }}}/>
)
)
}
/>
)
PrivateRoute.propTypes = {
isAuthenticated: PropTypes.bool.isRequired
}
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps)(PrivateRoute)
so, in DefaultsRoutes:
<Switch>
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<PrivateRoute component={Home} />
<Route path="*" component={NotFound404} />
</Switch>
in App:
let AppRedirect = ({ loadingApp }) => {
return loadingApp ? <Loading /> : <DefaultRoutes />;
};
const mapState = (state) => ({
loadingApp: state.app.loadingApp,
});
AppRedirect = connect(mapState)(AppRedirect);
export class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppRedirect />
</Provider>
);
}
}
export default App;
When user successfully logged in dispatch an action to change state: loadingApp: true -> loadingApp: false

React async Logincheck with PrivateRoute

I want to make a PrivateRoute in React with an async LoginCheck. I have tried this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedIn: false
}
}
componentDidMount() {
//ASYNC FUNCTION
setTimeout(() => {
this.setState({loggedIn: true});
}, 1000)
}
render() {
console.log("RENDERED", this.state.loggedIn);
const PrivateRoute = ({component: Component, ...rest}) => (
<Route {...rest} render={(props) => (
this.state.loggedIn === true
? <Component {...props} />
: <Redirect to="/login" />
)}/>
)
return(
<Router>
<Switch>
<Route path="/" exact component={Home} />
<PrivateRoute path="/private" component={PrivatePage} />
</Switch>
</Router>
)
}
}
But that is not working.
In the console it shows first "RENDERED false" and after a second "RENDERED true". So it is rendered with the right parameters but in the const PrivateRoute he is redirecting to the login page so loggedIn is false.
Why? What can I do?
You can create a function checkAuth() to seperate the validation as follow
import React from "react";
import {
BrowserRouter as Router,
Switch,
Redirect,
Route,
} from "react-router-dom";
import Home from "./Home";
import PrivatePage from "./Private";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedIn: true
}
}
componentDidMount() {
//ASYNC FUNCTION
setTimeout(() => {
this.setState({ loggedIn: false });
}, 1000)
}
render() {
console.log("RENDERED", this.state.loggedIn);
const checkAuth = () => {
try {
// To be replaced with your condition
if (this.state.loggedIn === true) {
return true;
}
} catch (e) {
return false;
}
return false;
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
checkAuth() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/' }} />
)
)} />
)
return (
<Router>
<Switch>
<Route path="/" exact component={Home} />
<PrivateRoute path="/private" component={PrivatePage} />
</Switch>
</Router>
)
}
}
Visit the url "http://localhost/private" as loggedIn: true this route is authorized but after 0.2s the state of loggedIn will be update it false and the protected route will be redirected to the home page.

What is best option to guard your routes in React?

I am using this HOC to guard my routes but I find odd using this HOC in every component because I am already using 1 or 2 HOC's like reduxForm etc
import React from "react";
import { connect } from "react-redux";
export default ChildComponent => {
class ComposedComponent extends React.Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push("/");
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
const mapStateToProps = state => {
return { auth: state.auth };
};
return connect(mapStateToProps)(ComposedComponent);
};
The HoC approach is right, but you should apply it to routes, not components.
Take a look at the pattern used in redux-auth-wrapper
I do not know how you implement your routes but there is clean solution for this.
render() {
let routes = (
<Switch>
<Route path="/auth" component={asyncAuth} />
<Route path="/" exact component={BurgerBuilder} />
<Redirect to="/" />
</Switch>
);
if (this.props.isAuthenticated) {
routes = (
<Switch>
<Route path="/checkout" component={asyncCheckout} />
<Route path="/orders" component={asyncOrders} />
<Route path="/logout" component={Logout} />
<Route path="/auth" component={asyncAuth} />
<Route path="/" exact component={BurgerBuilder} />
<Redirect to="/" />
</Switch>
);
}
return (
<div>
<Layout>
{routes}
</Layout>
</div>
);
}
And store the auth token in your redux.
const mapStateToProps = state => {
return {
isAuthenticated: state.auth.token !== null
};
};

Categories

Resources