How to have an array of objects based routing in react? - javascript

I am learning React. But I am familiar with Vue. In Vue with the Vue Router, we can have an array of objects based routing like,
const routes = [
{
name : "Login",
path : "/login",
component : ()=> import('views/Login.vue') //for lazy loading
meta : {
auth : false // this means no authentication needed for this particular route
}
},
{
name : "Dashboard",
path : "/dashboard",
component : ()=> import('views/Dashboard.vue') //for lazy loading
meta : {
auth : true // this means authentication needed for this particular route if the user is not authenticated, they will be redirected to login page
},
}]
What I tried so far is as below :
Login.jsx
const Login = () => {
const onClick = () => {
localStorage.setItem("token", "admin");
};
return <button onClick={onClick}>Login</button>;
};
export default Login;
Dashboard.jsx
const Dashboard = () => {
return <h1>Dashboard</h1>;
};
export default Dashboard;
App.js
import React, { Fragment } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import Dashboard from "./Dashboard";
import Login from "./Login";
const App = () => {
const routes = [
{
path: "/login",
component: Login,
auth: false,
},
{
path: "/dashboard",
component: Dashboard,
auth: true,
},
{
path: "/example",
component: Example,
auth: true,
},
];
return (
<>
<Switch>
{routes.map((route, index) => {
return (
<Fragment key={index}>
{route.auth ? (
<>
<Route
exact
path={`${route.path}`}
component={route.component}
/>
</>
) : (
<>
<Route
exact
path={`${route.path}`}
component={route.component}
/>
</>
)}
</Fragment>
);
})}
</Switch>
</>
);
};
export default App;
But in the above approach, I am always getting redirected to "/login". Is there anyways to fix this? Thanks in advance

Maybe this React-View-Router package help you. Instead of react-router-dom, try this package, and you can follow vue-router like syntax

First of all, you should not use Router in React like this.
video: https://www.youtube.com/watch?v=Law7wfdg_ls
Doc: https://reactrouter.com/
what is happening in your code is:
you are running a map and the first item is "login" that's why when the first item of your array came to the map it redirect to "login" and the moment this happens your application is switched to a different route and the next two iterations for your map will not execute.
this is how I do this in any application and it's really good for high scale applications:
(This is the sudo code)
// code for your router.js JSX
<BrowserRouter>
<Switch>
// you can define your other routes here
<AppRouter
path={"/profile"}
exact
layout={AuthLayout}
component={Profile}
/>
</Switch>
</BrowserRouter>;
// below functions ideally should written in a utility file
const AuthLayout = withRouter((props) => {
const [View, setView] = useState(null);
useEffect(() => {
setView(
// you can write your own check here
localStorage.getItem("access_token") ? (
props.children
) : (
<Redirect to="/" />
)
);
}, [props]);
return (
<div className="App-Main-Div">
<div className="main-section">
<Base header footer pathName={pathName}>
{View}
</Base>
</div>
</div>
);
});
const AppRouter = withRouter(
({ component: Component, layout: Layout, ...rest }) => (
<Route
{...rest}
render={(props) => (
<>
<Layout>
<Component {...props}></Component>
</Layout>
</>
)}
></Route>
)
);
// after doing all this you just need to put your router file in your App.js return statement
// which might look like this
const App =()=>{
// this router is your custom router you need to import it also
return <Router/>;
}

Related

How to use an array element as a route element?

I am writing routes with react router:
<Route path="/" element={<Homepage />} />
I have an array with the element names:
const page = ["Hompage", "About"];
How can I use the array element as a route element?
I tried to add strings of angle brackets and use the array element but it didn't work.
const edit = () => {
for (let i=0; i<10; i++) {
page[i]="<"+page[i]+" />"
}
Thanks
You will need to import the actual components you want to render at some point, and then map the array to JSX.
Example:
import HomePage from '../path/to/HomePage';
import About from '../path/to/About';
const pageMap = {
HomePage,
About
};
...
const pages = ["Hompage", "About"];
...
const edit = () => {
return pages.map(Page => <Page key={Page} />);
};
...
If you are wanting to map the pages to Route components, then it would be a similar process.
const pageMap = {
HomePage: { path: "/", element: <HomePage /> },
About: { path: "/about", element: <About /> },
};
const pages = ["Hompage", "About"];
...
pages.map(({ path, element }) => (
<Route key={path} path={path} element={element} />
))
At this point though, you may as well use the useRoutes hook and pass your routes config to it.
Example:
import { useRoutes } from 'react-router-dom';
import HomePage from '../path/to/HomePage';
import About from '../path/to/About';
...
const routesConfig = [
{ path: "/", element: <HomePage /> },
{ path: "/about", element: <About /> }
];
...
const routes = useRoutes(routesConfig);
...
return routes;
You should be able to do something like this:
import Home from '../Home'
import About from '../About'
const Routes = () => {
const pages = [{ route: '/', page: Home }, { route: '/about', page: About }]
return (
<Switch>
{pages.map(({route, page: Page }) => (
<Route path={route} element={<Page />} />
)}
</Switch>
)
}
The key here is to use the imported component as the value for your page key and then you can use normal JSX component syntax as you are iterating with map

React PrivateRoute auth route

I am working on a basic react auth app, right now the routes /signup and /login work when I run this repo with my .env.local file that contains firebase auth variables.
https://github.com/MartinBarker/react-auth-app
I am trying to make it so that the '/' route that points to Dashboard will only be accessible for a user who is currently signed in, and if a user is not signed in but tries to access the '/' route they will be redirected to the '/login' page.
But whenever I use the route
<PrivateRoute exact path="/" element={Dashboard} />
my chrome devtools console shows a blank page with error messages:
index.tsx:24 Uncaught Error: [PrivateRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
my PrivateRoute.js looks like this:
// This is used to determine if a user is authenticated and
// if they are allowed to visit the page they navigated to.
// If they are: they proceed to the page
// If not: they are redirected to the login page.
import React from 'react'
import { Navigate, Route } from 'react-router-dom'
import { useAuth } from '../Contexts/AuthContext'
const PrivateRoute = ({ component: Component, ...rest }) => {
// Add your own authentication on the below line.
//const isLoggedIn = AuthService.isLoggedIn()
const { currentUser } = useAuth()
console.log('PrivateRoute currentUser = ', currentUser)
return (
<Route
{...rest}
render={props =>
currentUser ? (
<Component {...props} />
) : (
//redirect to /login if user is not signed in
<Navigate to={{ pathname: '/login'}} />
)
}
/>
)
}
export default PrivateRoute
Im not sure why this error is occurring, any help is appreciated
This behaviour seems to have changed in ReactRouter V6 here is the solution we came up with for a project.
Private route
*Re-creating the users question code
import React from 'react'
import { Navigate, Route } from 'react-router-dom'
import { useAuth } from '../Contexts/AuthContext'
const PrivateRoute = ({ children }) => {
// Add your own authentication on the below line.
//const isLoggedIn = AuthService.isLoggedIn()
const { currentUser } = useAuth()
console.log('PrivateRoute currentUser = ', currentUser)
return (
<>
{
currentUser ? (
children
) : (
//redirect to /login if user is not signed in
<Navigate to={{ pathname: '/login'}} />
)
}
</>
)
}
export default PrivateRoute
Typescript
*Our actual code implementation of this issue
const PrivateRoute: React.FC = ({ children }) => {
const navigate = useNavigate();
const { isAuthenticated, isAuthLoading } = useAuth();
const { user, isLoadingUser } = useContext(UserContext);
// Handle users which are not authenticated
// For example redirect users to different page
// Show loader if token is still being retrieved
if (isAuthLoading || isLoadingUser) {
// TODO: show full page loader
return (
<div>Loading...</div>
);
}
// Proceed with render if user is authenticated
return (
<>
{children}
</>
);
};
Router
<Router>
<Routes>
<Route
path={routes.user.accountSignup.path}
element={
<PrivateRoute>
<AccountSignup />
</PrivateRoute>
}
/>
</Routes>
</Router>

Why doesn't react-router-dom useParams hook work within Context?

I've got a context shared amongst a group of profile pages. The context is responsible for loading and setting the user's profile from a database, like so:
const Profile = props => {
const { userProfile } = useContext(ProfileContext);
return userProfile && (
<div className="profile-container">
...stuff
</div>
);
};
export default Profile;
...routes:
<BrowserRouter>
<Header />
<main className="main-container">
<Switch>
...other routes
<ProfileContextProvider>
<Route path="/profile/:id" exact component={Profile} />
<Route path="/settings" exact component={Settings} />
</ProfileContextProvider>
</Switch>
</main>
<Footer />
</BrowserRouter>
The context itself is very simple:
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
export const ProfileContext = React.createContext({
userProfile: {},
setUserProfile: () => {}
});
ProfileContext.displayName = "ProfileContext";
const ProfileContextProvider = props => {
const { id } = useParams(); //always undefined!
const [userProfile, setUserProfile] = useState(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
...api call to load data
};
return (
<ProfileContext.Provider value={{
userProfile,
setUserProfile
}}>
{props.children}
</ProfileContext.Provider>
);
};
export default ProfileContextProvider;
However, that use of useParams() in there doesn't work. The "id" is always undefined. If I move this useParams() usage to the Profile component itself, it works fine. This does me no good, because I need to make a decision about the route, when loading data in the context, before I load all of Profile. I need it to work in the context!
Is this possible? Am I doing something wrong?
Context is itself different management and query param should props from the page to context set variable then it works
Your ProfileContextProvider is outside the route that defines the "id" param.... this cant work.

How can I protect a route in React router

Bhai log ye adnan ka pucha wa question hai jab m usko react sikha ra tha usko questions etc krne k liye m apna account diya tha cz pehly account verify krwany k liye baqaida coding challange pass krna hota tha (khud ko legit programmer show krwane k liye), Lekin ab kiu k log meri profile dekhty hain tu mujhe embarrassment hoti hai ;(
I'm using React with Node & express and authenticating with passport and passport local, I want to know that how can I protect a route in React, I mean I want to show a component only if user is authenticated and otherwise I want it to redirect on login page.
In my case I want to protect Notes route,
The method I'm trying is not working at all...
My React Code
import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import axios from 'axios';
import Register from './Components/Register';
import Login from './Components/Login';
import Notes from './Components/Notes';
function App () {
//Function to check authentication from server
const checkAuth = () => {
axios.get('http://localhost:8000/notes', { withCredentials : true })
.then(res => res.data)
}
return (
<Switch>
<Route exact path='/' component={Register} />
<Route exact path='/login' component={Login} />
<Route exact path='/notes' render={() => {
return checkAuth()? (
<Notes />
) : (
<Redirect to="/login" />
)
}} />
</Switch>
);
};
export default App;
And My server side code
//Notes Page
router.get('/notes', (req, res) => {
if(req.isAuthenticated()) {
res.send(req.user.email);
}else{
res.send(false);
}
};
Consider showing a loader until the api call returns a value. Once the api call has returned a value then render the required component using a higher order component.
App.jsx
class App extends Component {
state = {
login: false,
loading: false,
}
componentDidMount() {
//Function to check authentication from server
this.setState( {loadnig:true}, () => {
this.checkAuth()
}
}
checkAuth = () => {
// API request to check if user is Authenticated
axios.get('http://localhost:8000/notes', { withCredentials : true })
.then( res => {
console.log(res.data);
// API response with data => login success!
this.setState({
login: true,
loading:false
});
});
.catch( error => {
console.error(error);
// Handle error
this.setState({
loading:false
});
});
}
render() {
let {login, loading} = this.state
let protectedRoutes = null;
if(loading){
return <loader/>
}
return (
<Switch>
//use authorize HOC with three parameters: component, requiresLogin, isLoggedIn
<Route exact path='/path' component={authorize(component, true, login)} />
<Route exact path='/' component={Register} />
<Route exact path='/login' component={Login} />
</Switch>
);
}
};
export default App;
Using a Higher Order Component will give you the flexibility to control routes based on the login status of the users.
Authorize,jsx
export default function (componentToRender, requiresLogin, isLoggedIn) {
class Authenticate extends React.Component<> {
render() {
if (requiresLogin) {
//must be logged in to view the page
if (!isLoggedIn) {
// redirect to an unauthorised page or homepage
this.props.history.push('/unauthorised');
return;
// or just return <unauthoriesd {...this.props} />;
}
} else {
// to be shown to non loggedin users only. Like the login or signup page
if (isLoggedIn) {
this.props.history.push('/unauthorised');
return;
//or just return <home {...this.props} />;
}
}
//if all the conditions are satisfied then render the intended component.
return <componentToRender {...this.props} />;
}
}
return Authenticate;
}
Also, if you ever decide to add in more conditions for the routing then you can easily add those to the HOC.
checking login status via "server request" takes time! Consider then to create a Class with state!, i'll give you an example below:
import React, { Component } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import axios from 'axios';
import Register from './Components/Register';
import Login from './Components/Login';
import Notes from './Components/Notes';
class App extends Component {
state = {
login: false
}
componentDidMount() {
//Function to check authentication from server
this.checkAuth()
}
checkAuth = () => {
// API request to check if user is Authenticated
axios.get('http://localhost:8000/notes', { withCredentials : true })
.then( res => {
console.log(res.data);
// API response with data => login success!
this.setState({
login: true
});
});
}
render() {
let protectedRoutes = null;
if(this.state.login === true) {
// if login state is true then make available this Route!
protectedRoutes = <Route exact path='/notes' component={Notes} />
}
return (
<Switch>
<Route exact path='/' component={Register} />
<Route exact path='/login' component={Login} />
{ protectedRoutes }
</Switch>
);
}
};
export default App;
I hope this example will be useful to you.
Your function checkAuth since it makes a network call may not return the correct value in time for rendering, what you SHOULD do is create a state for whether or not the user is authenticated and have checkAuth update that state.
const [authenticated, updateAuthenticated] = useState(false);
...
update your checkAuth to update the authenticated state
//Function to check authentication from server
const checkAuth = () => {
axios.get('http://localhost:8000/notes', { withCredentials : true })
.then(res => updateAuthenticated(res.data)); // double check to ensure res.data contains the actual value returned
}
...
update your rendering to use the state
<Route exact path='/notes' render={() => {
return (authenticated ?
(<Notes />)
:
(<Redirect to="/login" />)
)
}} />
... with this approach you can also set the default in useState to true or false and determine whether or not it's your server side code that's giving trouble
... dont for get to call checkAuth in a useEffect somewhere at the top
useEffect(()=>{
checkAuth()
},[])
Replace your code below
<Route exact path='/notes' render={() => {
checkAuth()? (
<Notes />
) : (
<Redirect to="/login" />
)
}} />
With this code please
<Route exact path="/notes">{checkAuth ? <Notes /> : <Redirect to="/login" />}</Route>

Using react router, how can I pass component props using a route config?

I am trying to achieve the following using a single router config array. The thing to note here is that I have a state variable, storeName, which I am passing to the CategoryPage component.
import React, { useState } from 'react'
import { Switch, Route, BrowserRouter } from 'react-router-dom'
import { Navigation } from './Navigation'
import { HomePage } from './HomePage'
import { CategoryPage } from './CategoryPage'
import { ProductPage } from './ProductPage'
import { PageNotFoundPage } from './404Page'
export function App() {
const [storeName, setStoreName] = useState('My Store')
return (
<div>
<h1>{storeName}</h1>
<BrowserRouter>
<Switch>
<Route path="/category">
<CategoryPage storeName={storeName} />
</Route>
<Route path="/product">
<ProductPage />
</Route>
<Route path="/" exact>
<HomePage />
</Route>
<Route path="*">
<PageNotFoundPage />
</Route>
</Switch>
</BrowserRouter>
</div>
)
}
If I was to switch to using a route config object like so...
const routeConfig = [
{
path: '/',
exact: true,
component: HomePage,
},
{
path: '/category',
exact: false,
component: CategoryPage,
},
// ...
]
// ...
<Switch>
{routeConfig.map((route, i) => {
return (
<Route
path={route.path}
exact={route.exact}
key={i}
>
<route.component />
</Route>
)
})}
</Switch>
...what would be the most appropriate way to pass down props keeping in mind each component might need different props?
I suppose I could try to make the component key of the route config items a function which accepts every prop and then tries to map it to the component being returned. I'm not sure if this is the right way though.
Thanks!
Update #1
So I tried instead to return the component from the route config array like so:
const routeConfig = [
{
path: '/',
exact: true,
component: (props) => <HomePage {...props} />,
},
{
path: '/category',
exact: false,
component: (props) => {
return <CategoryPage {...props} />
},
},
]
// ...
const [storeName, setStoreName] = useState('My Store')
// ...
<Switch>
{routeConfig.map((route, i) => {
return (
<Route
path={route.path}
exact={route.exact}
key={i}
>
{route.component({ storeName })}
</Route>
)
})}
</Switch>
This is working, but every component now would have every prop. Like in this case my HomePage component doesn't need the storeName prop but it still has it. Once I start adding other components which need other state variables, maybe this could cause a lot of variables to be stored in memory? It doesn't seem ideal.
Update #2
It's possible I'm going in the wrong direction by using route configs. Seems like that is actually opposite to react router's philosophy as I understand it from here https://reacttraining.com/react-router/web/guides/philosophy. Maybe I'll stick with my first implementation w/o the route config, but it'd still be nice to know how using the route config and passing in the correct props can be achievable.
In your Update #1 just extract the props you want
const routeConfig = [
{
path: '/',
exact: true,
component: () => <HomePage />,
},
{
path: '/category',
exact: false,
component: ({storeName}) => {
return <CategoryPage storeName={storeName} />
},
},
]
Or you could use a list of relevant props for each component and extract them on the go
function pickProps(availableProps = {}, selection = []) {
return selection.reduce(
(props, selectedProperty) => {
props[selectedProperty] = availableProps[selectedProperty];
return props;
},
{}
);
}
// ...
const routeConfig = [{
path: '/',
exact: true,
component: HomePage,
},
{
path: '/category',
exact: false,
component: CategoryPage,
props: ['storeName'],
},
// ...
]
// ...
const componentProps = {storeName}
//....
<Switch>
{routeConfig.map((route, i) => {
return (
<Route
path={route.path}
exact={route.exact}
key={i}
>
<route.component
{...pickProps(componentProps, route.props)}
/>
</Route>
)
})}
</Switch>

Categories