React Router doesn't access Protected Route - javascript

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

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>
)

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>
);
}

Pass Props in a Private Route React

I'm trying to pass several props in a private route. What's the correct way to write this and what am I missing? Here is the code I have. My app works with this code, in that the user is able to login and see the dashboard. However, the props aren't passing. Is there a way to pass props to a private route?
<PrivateRoute exact path="/dashboard" component={Dashboard} render={routeProps =>
<Dashboard
handleUpdate={this.handleUpdate}
book={this.state.book}
info={this.state.info}
{...routeProps} />}
/>
Dashboard Component
class Dashboard extends Component {
state = {
book: this.props.book,
info: this.props.info,
error: '',
}
onLogoutClick = e => {
e.preventDefault();
this.props.logoutUser();
};
render() {
console.log(`BOOK STATE IN DB: ${this.state.book}`)
const { user } = this.props.auth;
return(
<div>
<h4>
<b>This is your page</b> {user.name}
</h4>
<button onClick={this.onLogoutClick}>Logout</button>
<h2>Search Book</h2>
<Search
handleUpdate={this.props.handleUpdate}
/>
<h4>Book Results</h4>
<div>{this.state.book}</div>
</div>
);
}
}
Dashboard.propTypes = {
logoutUser: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(
mapStateToProps,
{ logoutUser }
)(Dashboard);
Private Route
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
const PrivateRoute = ({ component: Component, auth, ...rest }) => (
console.log(auth),
<Route
{...rest}
render={props =>
auth.isAuthenticated === false ? (
<Redirect to="/login" />
) : (
<Component {...props} />
)
}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
Can you show us the code of PrivateRouter component? You can just follow the such way
<PrivateRoute exact path="/dashboard" component={Dashboard} props = {{book: this.state.book etc}}/>
And receive this props on PrivateRoute components to put it into child component
Can you try removing the component={Dashboard} prop, and only use the render prop to render the Dashboard. Your code should look like this
<PrivateRoute exact path="/dashboard" render={routeProps =>
<Dashboard
handleUpdate={this.handleUpdate}
book={this.state.book}
info={this.state.info}
{...routeProps} />}
/>
From the docs
Warning: <Route component> takes precedence over <Route render> so don’t use both in the same .
So, remove the component={Dashboard}
After the comments and the PrivateRoute code, i suggest you rewrite your PrivateRoute to
const PrivateRoute = ({ auth, ...rest }) => {
if (!auth.isAuthenticated) {
return <Redirect to="/login" />;
}
return <Route {...rest} />
);
and remove the component={Dashboard} part.
const PrivateRoute = ({component: Component, auth, book, handleUpdate, ...rest }) => (
console.log(rest),
console.log(book),
<Route
{...rest}
render={props =>
auth.isAuthenticated === false ? (
<Redirect to="/login" />
) : (
<Component book={book} handleUpdate={handleUpdate} {...props} />
)
}
/>
)

How do I hide navbar when i select login or signup in react?

I am trying to hide the Menu component when I select log in my app. I am working with react hooks and I have no idea how to do it.
My main looks like this :
<div>
<Menu/>
<Router>
{
domainList === "error" ?
(
<ErrorMessage
message="Error"
/>
)
:
Boolean(domainList) === true ?
(
<Main
endpoint={endpoint}
callbackReFetchDomains={reFetchDomains}
domainList={domainList}
hasDomainListError={hasDomainListError}
appendDomainList={appendDomainList}
changeDomainList={changeDomainList}
/>
)
:
(
<LoadingSpinner/>
)
}
</Router>
</div>
My main looks like this :
<>
<div>
{/*Switch will only render the first matched <Route/> child.*/}
<Menu/>
<Switch>
<Route path="/topics">
<ExampleComponentStructure/>
</Route>
<Route path="/login">
<Login/>
</Route>
<Route path="/domains">
<DomainList
endpoint={props.endpoint}
callbackReFetchDomains={props.callbackReFetchDomains}
domainList={props.domainList}
hasDomainListError={props.hasDomainListError}
appendDomainList={props.appendDomainList}
changeDomainList={props.changeDomainList}
/>
</Route>
<Route path="/signup">
<Signup/>
</Route>
<Route path="/users">
<UserMaintainList
endpoint={props.endpoint}
/>
</Route>
<Route path="/">
<StickerList
endpoint={props.endpoint}
callbackReFetchDomains={props.callbackReFetchDomains}
domainList={props.domainList}
hasDomainListError={props.hasDomainListError}
changeDomainList={props.changeDomainList}
/>
</Route>
</Switch>
</div>
</>
I know that the code is not clean. I am just starting with react and need some help doing this login screen. Thank you.
If i understood correct, you want to hide component Menu (is it navbar?). First you can check url you are in, by creating some flag, for example:
const isLogin = props.match.path === "/login"
And then just render component if it is false
{!isLogin && <Menu/>}
here is how i done
<PublicRoute exact path="/welcome" component={WelcomePageView} />
<PublicRoute exact path="/signin" component={SignInView} />
<PublicRoute
exact
path="/forgotPassword"
component={ResetPasswordView}
/>
<PublicRoute
exact
path="/resetPassword"
component={SetNewPasswordView}
/>
<RouteWithLayout
component={UserDashboardView}
exact
layout={MainLayout}
noAccess={true}
path="/dashboard"
/>
<RouteWithLayout
component={ProfileView}
exact
layout={MainLayout}
path="/Profile"
/>
PublicRoute.jsx
import React, { useEffect } from "react"
import { Route, Redirect } from "react-router"
import { connect } from "react-redux"
import { isUserAuthenticated, getAuthErrors, getAccessToken, getUserID } from "../selectors"
import { validateToken, logout } from "../actions/auth"
import { getPersistStatus } from "../selectors/persist"
const PublicRoute = ({
component: Component,
isAuthenticated,
authErrors,
access_token,
uid,
persistStatus,
logoutUser,
...rest
}) => {
useEffect(() => {
if (persistStatus && !isAuthenticated) {
logoutUser()
}
}, [persistStatus, isAuthenticated, logoutUser])
return (
<Route {...rest} render={props => (
!isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: "/dashboard",
state: { from: props.location }
}} />
)
)} />
)
}
const mapStateToProps = (state) => ({
isAuthenticated: isUserAuthenticated(state),
authErrors: getAuthErrors(state),
access_token: getAccessToken(state),
uid: getUserID(state),
persistStatus: getPersistStatus(state)
})
const mapDispatchToProps = (dispatch) => ({
logoutUser: () => {
dispatch(logout())
}
})
export default connect(mapStateToProps, mapDispatchToProps)(PublicRoute)
RouteWithLayout.jsx
import React from "react"
import { Route } from "react-router-dom"
import PropTypes from "prop-types"
import { useAuthorization } from "./Permissions"
const RouteWithLayout = (props) => {
const { layout: Layout, component: Component, path, noAccess, ...rest } = props
const userAccess = useAuthorization(path)
return (
<Route
{...{ path, ...rest }}
render={(matchProps) => (
<Layout>
{noAccess || userAccess.includes("R") ? (
<Component {...matchProps} />
) : (
<p>You do not have sufficient permissions to view this page</p>
)}
</Layout>
)}
/>
)
}
RouteWithLayout.propTypes = {
component: PropTypes.any.isRequired,
layout: PropTypes.any.isRequired,
path: PropTypes.string,
}
export default RouteWithLayout

React Router rendering blank pages with React Redux

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

Categories

Resources