Conditional Routes in react router dom v6 - reactjs

I am making a mern application with login/registration system and the dashboard and the other routes that will only be accessible when someone logs in now the problem is i was trying to write a if condition in react router dom so that all the routes inside the dashboard could be resitricted till the user logs in , and i use 'useNavigate' hook to go back to login page if user is not logged in but the application gives me error saying useNavigate() may be used only in the context of a component , but i used the same hook in my other component too where i didnt used any router and it worked fine there and so i am not able to understand what to do , also i want to know how can i put the component name inside a variable so that if i call the function i can put the component name in it and later put that varibale like this <component_name/> and it should change its value , here is my code:-
import Navbar from "./components/navbar/Navbar";
import Home from "./components/Home/Home";
import 'bootstrap/dist/css/bootstrap.min.css'
import Forgot_password from "./components/login_and_reg/Forgot_password/Forgot_password";
import Weather from "./components/Weather/Weather";
import Landing_page from './components/login_and_reg/Landing_page/Landing_page'
import {
BrowserRouter as Router,
Route,
Routes,
useNavigate
} from "react-router-dom";
import Verification from "./components/login_and_reg/Verification/Verification";
import Protected_routes from './components/Protected_routes'
import { useSelector } from "react-redux";
function App() {
const loggedin = useSelector(state => state.loggedin)
const Navigate = useNavigate();
const rememberMe = localStorage.getItem('rememberMe')
function checkroute(Component_name){
if (rememberMe=='true') {
return <Component_name/>
} else {
console.log(loggedin)
if(loggedin =='loggedin'){
return <Component_name/>
}
else{
Navigate('/')
}
}
}
return (
<>
<Router>
{/* <Navbar/> */}
<Routes>
<Route path="/weather" element={checkroute(Weather)}></Route>
<Route exact path="/" element={<Protected_routes/>}></Route>
<Route path="/verify/:first_name/:email" element={<Verification/>}></Route>
<Route path="/forgot_password" element={<Forgot_password/>}></Route>
{/* <Route exact path="/home" element={<Protected_routes/>}></Route> */}
</Routes>
</Router>
</>
);
}
I also made a protected route only for login purpose but i dont know how to use it for all the components if it is possible then here is code of that component:-
import React, { Component } from 'react';
import { useSelector } from 'react-redux';
import {Navigate, Route , useNavigate} from 'react-router-dom';
import Home from './Home/Home';
import Landing_page from './login_and_reg/Landing_page/Landing_page';
const Protected_routes = () => {
const loggedin = useSelector(state => state.loggedin)
const Navigate = useNavigate();
const rememberMe = localStorage.getItem('rememberMe')
if (rememberMe=='true') {
return <Home/>
} else {
if(loggedin=='loggedin'){
return <Home/>
}
else{
return <Landing_page/>
}
}
}
export default Protected_routes
export default App;

What you'd likely use a PrivateRoute component to wrap your secured pages. It will render the desired page if not logged in, or redirect to the login page.
Here's the flow:
1. Define a private route component:
// PrivateRoute.ts
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
import React from 'react';
export function PrivateRoute({ children }: { children: React.ReactElement }) {
// the user verification logic is up to your application
// this is an example based on your code above
const loggedin = useSelector(state => state.loggedin);
const rememberMe = localStorage.getItem('rememberMe');
if (rememberMe==='true') {
return <Navigate to={'/home'} />;
}
else if (loggedin==='loggedin'){
// render the wrapped page
return children;
}
else {
// user not logged in, redirect to the Login page which is unprotected
return <Navigate to={'/login'} />;
}
}
2. And use it in your router :
I assume you want to protect your Home and Wheater pages here. You can customize it to your own logic.
import React from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { PrivateRoute } from './PrivateRoute';
import { Home } from "./my-home-page";
import { Wheater } from "my-wheater-page";
import { Login } from "my-login-page";
export const AppRoutes = () => {
return (
<Routes>
<Route
path='home'
element={
<PrivateRoute>
<Home/>
</PrivateRoute>
}
/>
<Route
path='login'
element={
<Login/>
}
/>
<Route
path='wheater'
element={
<PrivateRoute>
<Wheater />
</PrivateRoute>
}
/>
</Routes>
);
};

Related

using react suspense and react react lazy each one inside of a different component ? one in app.tsx and the other inside of a private route?

I am trying to separate react lazy in 2 separate modules , due to the fact that I want to make my private route take multi components not only one.
my current app.tsx module looks like the following :
import React, { useState, useEffect, useContext, lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch, HashRouter, Redirect } from 'react-router-dom';
// Privite Route Component
import { PrivateRoute } from './components/routes/PrivateRoute';
import { NotFound } from './components/routes/NotFound';
// Components **********************************/
import Home from './components/Home/Home';
import { AuthAction } from './components/routes/AuthAction';
// Context API
import AuthProvider from './context/auth/AuthState';
// Lazy
const AppMain = lazy(() => import('././components/App/CentralHub'));
function App() {
return (
<Router>
<AuthProvider>
<Switch>
<Route exact path='/' component={Home} />
<PrivateRoute exact path='/app' component={AppMain} />
/>
<PrivateRoute exact path='/auth/action' component={AuthAction} />
<Route path='*' component={NotFound} />
</Switch>
</AuthProvider>
</Router>
);
}
export default App;
Would calling react suspense inside of a private route that receives a that target component as propose considered a bad idea? my private rout receives this component, as props from the app.tsx now the suspense action runs from the privet route, yet my component is getting imported still from out side form (the app.tsx) should I rather split private routes and lazy import the props component inside of the private route rather than from out side ?
or would my current model considered clean regardless of the fact that lazy is working in other component than suspense dose ?
import React, { useContext, useEffect, Suspense } from 'react';
import { Redirect, Route, useHistory, useLocation } from 'react-router-dom';
import { useAppSelector } from '../../app/hooks';
interface Props {
component: React.FC<any>;
exact?: boolean;
path: string;
}
export const PrivateRoute: React.FC<Props> = ({ component: Component, ...rest }) => {
const location = useLocation();
const authenticating = useAppSelector((state) => state.auth.authenticating);
const currentUser = useAppSelector((state) => state.auth.currentUser);
return (
<>
{location.pathname.startsWith('/app') && !authenticating && (
<Route
{...rest}
render={(props) =>
!currentUser?.authenticated /*Route Condtion*/ ? (
<Redirect to='/' />
) : (
<Suspense fallback={<div></div> /*null*/}>
<Component {...props} />
</Suspense>
)
}
/>
)}
</>
);
};

Passing props through route in reactjs

I am new to reactjs and have simple question. I want to pass auth props to Login component and want to update its value from within Login component. How can I pass auth to Login component and update its value from Login component.
Secondly, I have one protected route and only logged-in users can open that component. I added Private method to implement this. Is this right approach of making some components protected ?
import "./App.css";
import {
BrowserRouter,
Routes,
Route,
Switch,
Navigate,
NavLink,
} from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import RequestDemo from "./pages/RequestDemo";
import ProtectedRoute from "./Protected.Route";
const auth = true; //your logic
const Private = ({ Component }) => {
return auth ? <Component /> : <Navigate to="/login" />;
};
function App() {
return (
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/Login" element={<Login />} />
<Route
path="/RequestDemo"
element={<Private Component={RequestDemo} />}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
For auth variable I would suggest using as a State or context, below I have provided an example using context, you can do it with State also
import './App.css';
import { BrowserRouter,Routes, Route, Switch, Navigate, NavLink } from 'react-router-dom';
import Home from './pages/Home';
import Login from './pages/Login';
import RequestDemo from './pages/RequestDemo';
import ProtectedRoute from './Protected.Route';
import { useContext } from 'react';
// In the login Component
const LoginComponent = () => {
const authContext = useContext(MyContext);
const handleLogin = () => {
authContext.onAuthChange(true); // this will make the user login that change the value of auth to true
}
return (
<div>Login JSX</div>
)
}
const MyContext = React.createContext(null);
const Private = ({Component}) => {
const authContext = useContext(MyContext);
return authContext.auth ? <Component /> : <Navigate to="/login" />
}
function App() {
const [auth, setAuth] = React.useState(true);
const handleAuthChange = (newAuthState) => {
setAuth(newAuthState);
}
return (
<MyContext.Provider value={{
auth,
onAuthChange: handleAuthChange
}}>
<BrowserRouter>
<Routes>
<Route exact path='/' element={<Home />} />
<Route exact path='/Login' element={<Login />} />
<Route path='/RequestDemo' element={<Private Component={RequestDemo} />} />
</Routes>
</BrowserRouter>
</MyContext.Provider>
);
}
export default App;
1. In your case first you have to change auth from const to let as you want to change auth. Then you have to pass auth and an function which can update auth in the same file(where auth is declared) hence you will pass 2 props auth and function which updates auth.
From your code syntax I'm assuming you are using v6.
import "./App.css";
import {
BrowserRouter,
Routes,
Route,
Switch,
Navigate,
NavLink,
} from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import RequestDemo from "./pages/RequestDemo";
import ProtectedRoute from "./Protected.Route";
let auth = true; // you can mutate let and not constant
const authUpdate = (argument) => {
// update auth in this function and pass this function to the component
console.log(auth + 1);
// if you want some argument or parameter you can do so by declaring argument
console.log(argument); // only if you want to send some argument or else no need
};
const Private = ({ Component }) => {
return auth ? <Component /> : <Navigate to="/login" />;
};
function App() {
return (
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Home />} />
<Route
exact
path="/Login"
element={<Login auth={auth} authUpdate={authUpdate} />} // passed auth and authUpdate as props
/>
<Route
path="/RequestDemo"
element={<Private Component={RequestDemo} />}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
2. There's more than one way for protected route. Yes, you can do it like that as mentioned in code snippet.
PS: I suggest you to use state for auth if you want to trigger re-rendering and want to update auth value at parent and child component level both. If component doesn't re-render's then prop/variable won't be updated
import { useState } from "react";
const [auth,setauth]=useState(true)
const authUpdate=()=>{
// pre-steps
setauth(auth=>!auth)
// you should avoid passing setauth directly as a prop
// down the line in child component you can forget what setauth does and you can set something different
}
If you want to use certain variable/state in all child component you can look into Store or Global State concept. You can use react-redux or useContext which does not need to pass props from parent component to child component (prop drilling)

How to send the last value of State as a property

I just learn react and this is the problem I haven't been able to figure out for hours.
I have the App component and I want to pass data about user login to the ProtectedRoute component so I can handle access to the admin page.
In the App there is a function which manages lifting data up from the Login component (based on Firebase user authentication).
The problem is, the userLogged state is set too late so Protected route receives default value of the state.
How can I send the last state to the ProtectedRoute component?
import {useState} from "react"
import './App.css'
import Login from "./components/admin/Login"
import Homepage from "./components/homepage/Homepage"
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
import ProtectedRoute from "./ProtectedRoute"
import AdminPage from "./components/admin/AdminPage"
function App() {
const [userLogged, setUserLogged] = useState()
const getUser = (data) => {
setUserLogged(data)
console.log("data: " + data)
console.log("state: " + userLogged)
}
return (
<>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="/login" element={<Login sendUser={getUser}/>} />
<Route element={<ProtectedRoute isLoggedIn={userLogged} />}>
<Route path="/admin" element={<AdminPage />} />
</Route>
</Routes>
</>
);
}
export default App
This is the ProtectedRoute component, where I tried to add useEffect. It still doesn't work this way.
import { Navigate, Outlet } from "react-router-dom"
import {useState, useEffect} from "react"
const ProtectedRoute = (props) => {
const [isLogged, setIsLogged] = useState("")
useEffect(() => {
setIsLogged(props.isLoggedIn);
console.log(isLogged);
}, [props.isLoggedIn]);
return isLogged ? <Outlet /> : <Navigate to="/" />;
};
export default ProtectedRoute;

React JS React-router-dom Navigate not working

I try to redirect the user after login in my React App, but Navigate won't work and i don't know why...
Here is my code and thanks for your help
import React, { Component } from "react";
import {Route, Navigate} from 'react-router-dom';
import Bouton from "../components/Bouton";
class Dallan extends Component{
logout = () =>{
localStorage.removeItem('logged');
return <Navigate to= '/login' />;
}
render(){
return(
<Bouton typeBtn = 'btn-danger' click={() => this.logout()}>Deconnexion</Bouton>
)
}
}
export default Dallan;
And in my App.js
function App() {
let session = localStorage.getItem('logged');
return (
<BrowserRouter>
<Routes>
<Route path="/" element={session ? <Navigate to="/dallan" /> : <Login/>} />
<Route path='/dallan' element={<Dallan/>}/>
</Routes>
</BrowserRouter>
);
}
export default App;
If you are using react route dom v6 which I assume you are by the use of <Navigate /> then <Navigate /> is a component which would need to be rendered to work. You are just returning it to nothing so it obviously won't render. You want to use the useNavigate() hook instead. But you will want to use a function component to use the hook. Like so:
import React, { Component } from "react";
import {Route, useNavigate} from 'react-router-dom';
import Bouton from "../components/Bouton";
function Dallan() {
const navigate = useNavigate();
const logout = () =>{
localStorage.removeItem('logged');
navigate('/login')
}
return(
<Bouton typeBtn = 'btn-danger' click={() => logout()}>Deconnexion</Bouton>
)
}
export default Dallan;
technically it should work try writing it like so, return <Navigate replace={true} to='/' /> enssure that you place the replace={true} because its job is to redirect meaning it replaces everything on the page with where you are redirecting to user to

Why is withRouter used with connect () breaking my react app?

Good evening everyone,
I have been trying to add withRouter to my react app so it does not break because of the connect function (see code below).
My code is working, but when i add withRouter to the line below, it breaks my app with the following message :
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
Error: Invariant failed: You should not use <withRouter(Connect(App)) /> outside a Router>
i found this topic : Invariant failed: You should not use <Route> outside a <Router> but it's not helping me with me issue when i try to replace with a single Router
Please find below the whole code used :
App.js
import React, {useEffect}from 'react';
import { BrowserRouter } from 'react-router-dom'
import { Route, Redirect} from 'react-router-dom'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import * as actions from './store/actions/index'
// Composants
import Layout from './components/hoc/Layout/Layout'
import BudgetHandler from './components/BudgetHandler/BudgetHandler'
import DashBoard from './components/DashBoard/DashBoard'
import Movements from './components/Movements/Movements'
import Home from './components/Home/Home'
import Logout from './components/Logout/Logout'
import classes from './App.module.css'
const App = (props) => {
useEffect(() => {
props.onTryAutoSignup()
},[])
let routes = <React.Fragment>
<Route path="/" exact component={Home} />
<Redirect to="/" />
</React.Fragment>
if(props.isAuthentificated) {
routes = <React.Fragment>
<Route path="/movements" component={Movements} />
<Route path="/dashboard" component={DashBoard} />
<Route path="/logout" component={Logout} />
<Route path="/handler" component={BudgetHandler} />
<Redirect to="/dashboard" />
</React.Fragment>
}
return (
<div className={classes.App}>
<BrowserRouter>
<Layout>
{routes}
</Layout>
</BrowserRouter>
</div>
);
}
const mapStateToProps = state => {
return {
isAuthentificated: state.auth.token !== null
}
}
const mapDispatchToProps = dispatch => {
return {
onTryAutoSignup: () => dispatch(actions.authCheckState())
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
And this is happening because i am trying to add this function to the useEffect hook to check permanently if the user is auth or not :
in actions/auth.js
export const authCheckState = () => {
return dispatch => {
const token = localStorage.getItem('token')
if(!token) {
dispatch(logout())
} else {
const expirationTime = new Date(localStorage.getItem('expirationDate'))
const userId = localStorage.getItem('userId')
if(expirationTime > new Date()){
dispatch(logout())
} else {
dispatch(finalSignIn(token, userId))
dispatch(checkAuthTimeout(expirationTime.getSeconds() - new Date().getSeconds()))
}
}
}
}
Thank you for your help
Have a good evening
withRouter can only be used in children components of element. In your case can be used with Movements, DashBoard and other childrens. use it while exporting Movements like
export default withRouter(Movements)
on Movements page.

Resources