React Router - Should I render all the routes at once? - javascript

Suppose I have an application, where you can login both as user or admin and I have this routes:
Log In
Register
Home
Admin panel (only available to admins)
My Profile (only available to users)
I would have a switch with routes like this:
<Switch>
<UnloggedRoute exact path='/login'>
<Login />
</Route>
<UnloggedRoute exact path='/register'>
<Register />
</Route>
<PrivateRoute exact path='/'>
<HomePage />
</Route>
<AdminRoute exact path='/admin'>
<Admin />
</Route>
<UserRoute exact path='/profile'>
<Profile />
</Route>
</Switch>
So inside PrivateRoute I have a conditional that checks if the user is logged in, if not, it redirects to /login. In AdminRoute it checks if the user is admin, if not, redirects to / and in UserRoute I have a conditional that checks if the user is a normal user, if not, redirects to /.
Finally, in the UnloggedRoute I have a check to see if the user is logged in. If it is, then it goes to /.
So my question is: should I leave the switch like it is, or should I not render the route "Profile" if the user is an admin? Like this:
user.isAdmin() && (
<Route exact path='/admin'>
<Admin />
</Route>
)
I feel like the first way is more declarative and easier to understand and manage, but I want to hear opinions...

So for my app, I did something pretty similar to how you did it in the first way. I had to deal with 4 different logins using 5 different routes being: MainRoute, TeacherRoute, StudentRoute, TutorRoute, and AdminRoute. For my uses, I simply had each one of these routes check the accountType (or isAdmin() in your case) within the route itself like so:
const PrivateRouteAdmin = ({ component: RouteComponent, ...rest }) => {
const { currentUser } = useContext(AuthContext);
//If login is good, allow access or redirect to login
if (!!currentUser) {
return (
<Route
{...rest}
render={routeProps =>
(currentUser.isAdmin()) ? (
<RouteComponent {...routeProps}
currentUser={currentUser} />
) : (
<Redirect to={"/incorrect-login"} />
)
}
/>
)
} else {
//Bad login
return (
<Route
{...rest}
render={() => (
<Redirect to={"/login"} />
)}
/>
)
}
}
export default PrivateRoutedAdmin;
As long as you check the routes like so within the routes, you don't need to do the isAdmin() within the Switch.
Also, to keep things a bit more organized within your Switch, I would do:
<AdminRoute exact path='/admin' component={Admin} />

Related

Can I keep the token persistent on protected routes?

Sorry if the question is badly phrased. I am working with React.js and Parse Server at school. The issue is the following:
I would like to have protected routes, just for users that are currently logged in, and I am trying to do it by using the .getSessionToken() Parse built-in function. I retrieve the token successfully, get access to the protected routes only if I am logged in, but as soon as I refresh the page, the route is inaccessible again because the token is updated.
I have a working solution, but it creates some issues at the moment. The issue is that even if I have access on the page, there's the following error in the console: No routes matched location "/page1"
Do you have any idea of how I can prevent the token from updating or any other more elegant solution for this issue?
My code is the following:
1.When logging in, I pass a setter using useState() through props, all the way to the App.js where the routes are. The only reason I set the token here is in order to navigate to the next page which is protected.
await Parse.User.logIn(username, password);
const currentUser = Parse.User.current();
const currentToken = currentUser.getSessionToken();
setSessionToken(currentToken);
navigate("/page1");
2.Here, I am checking if a user is currently in my local storage, and if yes, get the token associated with the user.
//sessionToken = the one I pass with props from step 1
const [sessionToken, setSessionToken] = useState();
//This makes sure that I get the token again and again if page is refreshed
const [refreshedToken, setRefreshedToken] = useState();
const authenticateUser = async () => {
if (Parse.User.current() !== null) {
const token = Parse.User.current().getSessionToken();
setRefreshedToken(token);
}
};
useEffect(async () => {
await authenticateUser();
}, []);
3.Since I have 2 state variables now, I make a conditional rendering by using both.
<Route
path="/login"
element={<LoginPage setSessionToken={setSessionToken} />}
/>
{(sessionToken !== undefined || refreshedToken !== undefined) && (
<Fragment>
<Route path="/page1" element={<Page1 />} />
<Route path="/page2" element={<Page2 />} />
<Route path="/page3" element={<Page3 />} />
I believe the issue here is that you are attempting to navigate to a path "/page1" before the component rendering the routes knows it should be rendering them, hence the No routes matched location "/page1" error.
You may want to create an AuthWrapper component that checked the sessionStorage and token and renders an Outlet for nested routes you want to protect, otherwise redirect the users to another page, i.e. to home or login.
import { Outlet, Navigate, useLocation } from 'react-router-dom';
const AuthWrapper = (props) => {
const location = useLocation();
... business logic to check sesstionStorage/token ...
return (sessionToken !== undefined || refreshedToken !== undefined)
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />
};
Now declare the routes, wrapping the routes you want to protect in the AuthWrapper layout component.
<Route
path="/login"
element={<LoginPage setSessionToken={setSessionToken} />}
/>
<Route element={<AuthWrapper /* pass any session props here */ />}>
<Route path="/page1" element={<Page1 />} />
<Route path="/page2" element={<Page2 />} />
<Route path="/page3" element={<Page3 />} />
</Route>

how can i use array in routing with react-router

This is my routing with regex pattern by react-router.
ItemList component appears when the URL is /categories/cat1 or /categories/cat2 or /categories/cat3 or /categories/cat4 or /categories/cat5 or /categories/cat6 otherwise NotFound component appears.
function App() {
return (
<Router>
<Header />
<Container >
<Switch>
<Route exact path='/' component={Home} />
<Route path='/categories/(cat1|cat2|cat3|cat4|cat5|cat6)' component={ItemList} />
<Route path="*" component={NotFound} />
</Switch>
</Container>
<Footer />
</Router>
);
}
I want to use dynamic array for ItemList routing like this:
const itemCategories = ['cat1','cat2','cat3','cat4','cat5','cat6'];
in this path:
path='/categories/(cat1|cat2|cat3|cat4|cat5|cat6)'
If I understand your question you want to render a route that handles multiple categories.
Render a single path with a match parameter. This allows you to dynamically handle any category value.
<Route path='/categories/:category' component={itemList} />
Render a single route with a path array. This allows you to handle specifically allowed categories. This results in path={["/categories/cat1", "/categories/cat2", ... , "/categories/cat6"]}.
<Route
path={itemCategories.map(cat => `/categories/${cat}`)}
component={itemList}
/>
Render a single route with a path array and map in the regex. This results in path='/categories/(cat1|cat2|cat3|cat4|cat5|cat6)' as you were looking for.
<Route
path={`/categories/(${itemCategories.map((cat) => cat).join("|")})`}
component={ItemList}
/>
Render a route for each category. This allows you to handle specifically allowed categories but basically duplicates the Route component.
{
itemCategories.map(cat => (
<Route key={cat} path={`/categories/${cat}`} component={itemList} />
))
}
I want to route just for cat1 to cat6. If user types something else
like categories/cat7 notFound page will appear.
For this I would suggest option 2 or 3.
You can use : to set it to a dynamic route like this:
<Route path="/categories/:cat" component={itemList} />
and then render the route according to the users choice
Or you can map the routes like this:
{itemCategories.map(route => {
return <Route path=`/categories/${route}}` component={itemList}/>
}}
I'm not sure what you want so I gave both options

ReactJS Login page repeating on every url path

Here is my code, I am completely new to front end development can any one help me solve the issue, I want the user to be redirected to the login page if the user is not logged in but once he is logged in every thing should work fine but even when I click sign in, the login page shows up when I change the URL, The login page is appearing on every URL
Here's my code (i am new to front end pls dont judge)
import { useState } from 'react'
import { Route, Switch} from 'react-router-dom'
import Dashboard from "./pages/Dashboard";
import DatasetGenerator from "./pages/DatasetGenerator"
import Simulator from "./pages/Simulator"
function App() {
const [login, setlogin] = useState(true)
const [homepage, setHomepage] = useState(false)
const loginHandler = ()=>{
if(login == true){setlogin(false)
setHomepage(true)}
}
return (
<div>
{login && <SignIn loginHandler={loginHandler} />}
<Switch>
<Route path='/' exact>
{homepage && <Dashboard />}
</Route>
<Route>
{homepage && <DatasetGenerator path="/dataset-generator"/>}
</Route>
<Route path="/simulator">
{homepage && <Simulator />}
</Route>
</Switch>
</div>
)
}
export default App;
Seems like you want to conditionally render the login component OR the rest of your app. Since the login state and the homepage state appear to be mutually exclusive you probably don't need both (though perhaps we're missing some context).
return (
<div>
{login ? (
<SignIn loginHandler={loginHandler} />
) : (
<Switch>
<Route path='/' exact>
<Dashboard />
</Route>
<Route>
<DatasetGenerator path="/dataset-generator"/>
</Route>
<Route path="/simulator">
<Simulator />
</Route>
</Switch>
)}
</div>
)
A better solution would be to implement an auth workflow by creating authenticated route components that handle redirecting to a login route if not authenticated, otherwise allows the user to access the route.

Routes not working as desired in React JS

I am trying to build a full stack application with User login/logout functionality.
I want to protect certain pages such that they can only be viewed when the user is logged in. For login I have created a REST API and I am using session storage to keep track of whether the user is logged in or not.
validateUser = () => {
let user = {
username: this.state.email,
password: this.state.password,
//status: "LOGGED_IN"
};
UserService.authenticateUser(user).then((res) => {
if(res.data === 'SUCCESS') {
window.sessionStorage.setItem("isUserLogged", true);
} else if(res.data === 'FAILURE') {
window.sessionStorage.setItem("isUserLogged", false);
this.resetLoginForm();
this.setState({"error":"Invalid username or password"});
}
})
};
Tis is my App.js
function App() {
return (
<div>
<Router>
<HeaderComponent/>
<div className="container">
<Switch>
<Route path="/" exact component={LandingPageComponent}></Route>
{/* <Route path ="/customers" component = {ListCustomerComponent}></Route> */}
{/* <Route path ="/add-customer/:id" component = {CreateCustomerComponent}></Route> */}
<Route path = "/view-customer/:id" component = {ViewCustomerComponent}></Route>
<Route path = "/admin-login" component = {AdminLoginComponent}></Route>
<Route path = "/admin-register" component = {AdminResgisterComponent}></Route>
<Route path="/customers" exact render={() => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <ListCustomerComponent />
: <Redirect to='/admin-login' />
)} />
<Route path="/add-customer/:id" exact render={() => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <CreateCustomerComponent />
: <Redirect to='/admin-login' />
)} />
</Switch>
</div>
<FooterComponent/>
</Router>
</div>
);
}
export default App;
Everything works fine if I don't check my session storage. But when I try to implement the conditional routes as shown above I start getting errors.
If I just put simple routes, then I don't encounter this error.
Any help would be highly appreciated.
You didn't pass Route props into your component. So history does not included in props, you can console.log(this.props) to check what this.props contains.
To fix it, let's pass Route props into your components
<Route path="/add-customer/:id" exact render={(props) => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <CreateCustomerComponent {...props} /> // ADD PROPS HERE
: <Redirect to='/admin-login' />
} />
You didn't show what you did on ListCustomerComponent.
You could try to encapsulate your component using HOC withRouter or if you are using Functional component, use useHistory hook.
// on export class component
export default withRouter(YourComponent)
in functional component, you can use
const YourComponent = ()=>{
const history = useHistory();
// then you can say something such as
// history.push(...)
return <>...your view here...</>
}
export default YourComponent;
<Switch>
{/* Login Sections goes Here */}
<Route exact path='/' component={MainPage} />
<Route exact path='/login' component={Login} />
<Route exact path='/admin/' component={LoginAdmin} />
<Route exact path='/register' component={Register} />
{/* AdminUser ROutes goes here */}
<SuperUserDashboard>
<Route exact path='/admin/dashboard' component={Dashboardpage} />
<Route exact path='/admin/users' component={UsersAdmin} />
</SuperUserDashboard>
<Route exact path='' component={Notfound} />
</Switch>
in superuser dashboard check if user is authenticated if not redirect to admin login page else all the routes will be visible

Prioritise nested route over main route

Setup
I have an App component rendering following routes:
<Route path="/items/:id" component={ItemDetail} />
<Route path="/items" component={AllItems} />
In my AllItems component I render a list of all items and the option to create a new item or update an existing one. Doing either one of those actions opens a popup. To do this I render following routes in AllItems:
<Route path="/items/add" component={AddItemModal} />
<Route path="/items/edit" component={EditItemModal} />
Note: It's important that these modals are actually linked to these routes, I can't change that. Neither can I render those routes outside of AllItems as I need to pass soms props to the modals.
Problem
When I go to a route like /items/1: ItemDetail renders (as expected).
When I go to /items/add: ItemDetail renders with add as :id.
I need it to render AddItemModal here as defined in AllItems.
What I tried:
I tried adding exact to the /items/:id route and I also tried adding it to /items/add & /items/edit. Neither of those solutions worked. Either only ItemDetail rendered, or only the modals.
I tried defining /items before /items/:id to hopefully give higher priority to the nested routes. ItemDetail never rendered in this case.
Is there a solution to this so I can prioritise items/add & items/edit over items/:id
Try nesting the routes under /items
<Route
path="/items"
render={() => (
<>
<Route path="" component={AllItems} exact />
<Route path="/add" component={AddItemModal} />
<Route path="/edit" component={EditItemModal} />
<Route path="/:id" component={ItemDetail} />
</>
)}
/>
If you want to have an independent views for ItemDetail and AllItems but at the same time have /items/add and /items/:id/edit (took a little liberty with the url, you need and id to edit an item right?) as modals over AllItems so the structure of the routes would be something like this:
AllItemsView (/items)
AddItemModal (/items/new)
EditItemModal (/items/:id/edit)
ItemDetailView (/items/:id)
You need a little tweak of Tnc Andrei response:
<Route
path="/items"
render={({ match: {url, isExact}, location: {pathname} }) => {
let pathnameArray = pathname.split("/")
let lastChunk = pathnameArray[pathnameArray.length - 1]
if (isExact || lastChunk === "new" || lastChunk === "edit") {
return (
<>
<Route path={`${url}/`} component={CompetitionsView} />
<Switch>
<Route path={`${url}/new`} component={CompetitionFormModal} />
<Route path={`${url}/:competitionId/edit`} component={CompetitionFormModal} />
</Switch>
</>
)
}
return (
<>
<Route path={`${url}/:competitionId`} component={CompetitionView} />
</>
)
}}
/>

Categories