Route guard while using ionic + react - reactjs

I want to redirect the unauthenticated user to /login. I have a property in my context called loggedIn, when it is false I want to make a redirect to HomePage i.e "/".
I just got confused due to the route structure. I'm seeing something like a protected route everywhere.
import React, { useContext, useEffect } from "react";
import { Redirect, Route } from "react-router-dom";
import { IonApp, IonRouterOutlet, IonSplitPane } from "#ionic/react";
import { IonReactRouter } from "#ionic/react-router";
import { setupConfig } from "#ionic/react";
import { Plugins, Capacitor } from "#capacitor/core";
import { useHistory } from "react-router-dom";
import Home from "./pages/Home/Home";
/* Core CSS required for Ionic components to work properly */
import "#ionic/react/css/core.css";
/* Basic CSS for apps built with Ionic */
import "#ionic/react/css/normalize.css";
import "#ionic/react/css/structure.css";
import "#ionic/react/css/typography.css";
/* Optional CSS utils that can be commented out */
import "#ionic/react/css/padding.css";
import "#ionic/react/css/float-elements.css";
import "#ionic/react/css/text-alignment.css";
import "#ionic/react/css/text-transformation.css";
import "#ionic/react/css/flex-utils.css";
import "#ionic/react/css/display.css";
import "../src/utils/spacing.css";
/* Theme variables */
import "./theme/variables.css";
/* Components */
import Dashboard from "./pages/Dashboard/Dashboard";
import SideMenu from "./components/SideMenu/SideMenu";
import LoginPage from "./pages/Login/Login";
import SignupPage from "./pages/Signup/Signup";
import Create from "./pages/Create/Create";
import Edit from "./pages/Edit/Edit";
import { AuthContext, AuthProvider } from "./providers/context/AuthContext";
const App: React.FC = () => {
const history = useHistory();
return (
<IonApp>
<AuthProvider>
<IonReactRouter>
<IonSplitPane contentId="main">
<SideMenu />
<IonRouterOutlet id="main">
<Route path="/dashboard/:name" component={Dashboard} exact />
<Route path="/dashboard/Edit/:id" component={Edit} exact />
// if !user navigate to login component
<Route path="/create" component={Create} exact />
<Route path="/signup" component={SignupPage} exact />
<Route path="/" component={Home} exact />
<Redirect from="/dashboard" to="/dashboard/Home" exact />
</IonRouterOutlet>
</IonSplitPane>
</IonReactRouter>
</AuthProvider>
</IonApp>
);
};
export default App;

Might be useful for anyone: is solved it by using the Private Route and pubic Route trick
I Created a component called PrivateRoute.tsx and added the following:
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { EnrolleeContext } from "../providers/context/EnrolleeContext";
const PrivateRoute = ({ component: Component, ...rest }: any) => {
const { loggedIn } = useContext(EnrolleeContext);
return (
// Show the component only when the user is logged in
// Otherwise, redirect the user to /signin page
<Route
{...rest}
render={(props) =>
loggedIn ? <Component {...props} /> : <Redirect to="/" />
}
/>
);
};
export default PrivateRoute;
Also created a Public Route, added restricted to be able to restrict Components if restricted == true, meaning the Route is Private, if false, otherwise.
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { EnrolleeContext } from "../providers/context/EnrolleeContext";
const PublicRoute = ({ component: Component, restricted, ...rest }: any) => {
const { loggedIn } = useContext(EnrolleeContext);
return (
// restricted = false meaning public route
// restricted = true meaning restricted route
<Route
{...rest}
render={(props) =>
loggedIn && restricted ? (
<Redirect to="/dashboard/Home" />
) : (
<Component {...props} />
)
}
/>
);
};
export default PublicRoute;
Updated App.tsx
<PublicRoute restricted={false} component={Home} path="/" exact />
<PublicRoute
restricted={true}
component={LoginPage}
path="/login"
exact
/>
<PrivateRoute
component={Dashboard}
path="/dashboard/:name"
exact
/>
<PrivateRoute component={Create} path="/create" exact />
<PublicRoute
restricted={false}
component={ForgotPassword}
path="/forgot-password"
exact
/>
Other Routes like forgot password is open to the public`, while Create is Private, so it is at your own discretion.

Related

Protect the react routes with two different layouts childrens

I am protecting the routes and making sure the if no route found or if the user does not loggedin, then redirects the user to login page. I have tried a lot solutions but nothing work for me.I have tried with the variable but I got nothing, the user does not navigate to login screen if const a !== 'admin',
All I wanted if const a === 'admin' then navigate to routes and if no route found then navigate to '/' path and if const a !== 'admin' then navigate to '/login'
App.js code
enter code hereimport { useSelector } from 'react-redux';
import { ThemeProvider } from '#mui/material/styles';
import { CssBaseline, StyledEngineProvider } from '#mui/material';
// routing
// import Routes from 'routes';
// defaultTheme
import themes from 'themes';
// project imports
import NavigationScroll from 'layout/NavigationScroll';
// Alert toastify
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { Navigate } from 'react-router';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import MainLayout from './layout/MainLayout';
import AuthLayout from './layout/MinimalLayout';
import DashboardDefault from './views/dashboard/Default';
import Orders from './views/pages/orders/orders';
import MenuPage from './views/pages/Menu/Menu';
import PromoCodesPage from './views/pages/promocodes/PromoCodes';
import LoginPage from './views/pages/authentication/authentication3/Login3';
// ==============================|| APP ||============================== //
const App = () => {
const customization = useSelector((state) => state.customization);
const a = 'admin';
return (
<StyledEngineProvider injectFirst>
<ToastContainer />
<ThemeProvider theme={themes(customization)}>
<CssBaseline />
<NavigationScroll>
<Routes>
{a === 'admin' ? (
<Route path="/" element={<MainLayout />}>
<Route path="/" element={<DashboardDefault />} />
<Route path="/dashboard/default" element={<DashboardDefault />} />
<Route path="/orders" element={<Orders />} />
<Route path="/menu" element={<MenuPage />} />
<Route path="/promocodes" element={<PromoCodesPage />} />
</Route>
) : (
<Route path="/" element={<AuthLayout />}>
<Route path="/login" element={<LoginPage />} />
</Route>
)}
</Routes>
</NavigationScroll>
</ThemeProvider>
</StyledEngineProvider>
);
};
export default App;

React Typescript Error: Invariant failed: You should not use <withRouter(App) /> outside a <Router>

I was developing an App where I use Firebase as an Authentication system of the App, an when I try to implement the routes of the app, I start to get the above title error.
I'm using withRouter funtion, to encapsulate my App component.
So the code of my App.tsx file is the following:
import React, { FC, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
Route,
Switch,
useHistory,
withRouter,
BrowserRouter as Router,
} from "react-router-dom";
import "./App.css";
import Header from "./components/sections/Header";
import SignUp from "./components/pages/SignUp";
import SignIn from "./components/pages/SignIn";
import ForgotPassword from "./components/pages/ForgotPassword";
import Homepage from "./components/pages/Homepage";
import Dashboard from "./components/pages/Dashboard";
import PrivateRoute from "./components/auth/PrivateRoute";
import PublicRoute from "./components/auth/PublicRoute";
import Loader from "./components/UI/Loader";
import firebase from "./firebase/config";
import {
getUserById,
setLoading,
setNeedVerification,
} from "./store/actions/authActions";
import { RootState } from "./store";
const App: FC = () => {
const dispatch = useDispatch();
const { loading } = useSelector((state: RootState) => state.auth);
let history = useHistory();
// Check if user exists
// App.tsx
useEffect(() => {
dispatch(setLoading(true));
const unsubscribe = firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
await dispatch(getUserById(user.uid));
if (user.emailVerified) {
history.push("/homepage");
} else {
history.push("/signin");
dispatch(setNeedVerification());
}
}
dispatch(setLoading(false));
});
return () => {
unsubscribe();
};
});
if (loading) {
return <Loader />;
}
function Routes() {
return (
<Switch>
<PublicRoute path="/signup" component={SignUp} exact />
<PublicRoute path="/signin" component={SignIn} exact />
<PublicRoute path="/forgot-password" component={ForgotPassword} exact />
<PrivateRoute path="/dashboard" component={Dashboard} exact />
<PublicRoute path="/homepage" component={Homepage} exact />
</Switch>
);
}
return (
<Router>
<Header />
<Routes />
</Router>
);
};
export default withRouter(App);
`So I think that have to be realated with the configuration of Route library into the main component of the app.
What I missing??
Take thankss in advance !
App is the component rendering the Router so App itself can't use anything that requires a Router context higher up in the React tree.
The solution is to move the Router higher in the React tree, i.e. wrap App in the Router. Once App is being rendered by a Router and since you are using the useHistory hook there will be no need to decorate with the withRouter HOC.
App
const App: FC = () => {
...
function Routes() {
return (
<Switch>
<PublicRoute path="/signup" component={SignUp} exact />
<PublicRoute path="/signin" component={SignIn} exact />
<PublicRoute path="/forgot-password" component={ForgotPassword} exact />
<PrivateRoute path="/dashboard" component={Dashboard} exact />
<PublicRoute path="/homepage" component={Homepage} exact />
</Switch>
);
}
return (
<>
<Header />
<Routes />
</>
);
};
export default App;
index where App is rendered.
import { BrowserRouter as Router } from "react-router-dom";
import App from '../App';
...
<Router>
<App />
</Router>

When should Redirect be called in react?

My react component should work as follows:
Check a global variable for error and if there is error redirect to home and unset the global error variable.
Otherwise it should render a which will use current page's location to redirect to correct location.
How should I go about doing this. The only way which somehow works is if I do conditional render and set global variable in render. Is there any better way?
In this example I'm using context api to share the state across all routes available.
For question No. 1 - use protected route. (Much simpler & neat)
predeclared path & component on <ProtectedRoute path="/anyPath" component={anyComponent}/>
if you want to have flexibility on setting the path yourself, then go to methods No. 2 below
in App.js:-
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { ErrorState } from "./contexts/ErrorState";
// import ProtectedRoute from "./comps/routes/ProtectedRoute";
import ProtectedRouteCustom from "./comps/routes/ProtectedRouteCustom";
import Home from "./comps/Home";
import Demo from "./comps/Demo";
import Dashboard from "./comps/Dashboard";
import "./style.css";
export default function App() {
return (
<ErrorState>
<Router>
<NavBar />
<Switch>
<Route exact path="/" component={Home} />
<Demo exact path="/demo" component={Demo} />
{/*} <ProtectedRoute exact path="/demo/dashboard" component={Dashboard} /> */}
<ProtectedRouteCustom path="/demo" />
<Route path="*" component={() => "Not Found"} />
</Switch>
</Router>
</ErrorState>
);
}
ProtectedRoute.js (emphasis more on Route render):-
import React from "react";
import { Redirect, Route } from "react-router-dom";
import { useError } from "../../contexts/ErrorState";
const ProtectedRoute = ({ component: Component, ...rest }) => {
const [errorState, errorDispatch] = useError();
const { error } = errorState;
return (
<Route
{...rest}
render={props => {
// you can use props.location.pathname to redirect user to the route path you have specified in App.js (see in console.log)
console.log(props);
// render Dashboard component if 'error' === false
if (!error) return <Component {...props} />;
// redirect to homepage if error === false
else
return (
<Redirect
to={{
// specify the path to redirect (if condition 'error' === true)
pathname: "/",
state: { from: props.location }
}}
/>
);
}}
/>
);
};
export default ProtectedRoute;
For question No. 2 - you can build your own custom protected route
path can be specified or set it yourself in ProtectedRouteCustom
App.js:-
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { ErrorState } from "./contexts/ErrorState";
// import ProtectedRoute from "./comps/routes/ProtectedRoute";
import ProtectedRouteCustom from "./comps/routes/ProtectedRouteCustom";
import Home from "./comps/Home";
import Demo from "./comps/Demo";
import Dashboard from "./comps/Dashboard";
import "./style.css";
export default function App() {
return (
<ErrorState>
<Router>
<NavBar />
<Switch>
<Route exact path="/" component={Home} />
<Demo exact path="/demo" component={Demo} />
{/*} <ProtectedRoute exact path="/demo/dashboard" component={Dashboard} /> */}
<ProtectedRouteCustom path="/demo" />
<Route path="*" component={() => "Not Found"} />
</Switch>
</Router>
</ErrorState>
);
}
custom protected route:- (emphasis more on Redirect rather than Route render)
import React from "react";
import { Redirect, Route } from "react-router-dom";
import { useError } from "../../contexts/ErrorState";
import Dashboard from "../Dashboard";
const ProtectedRouteCustom = ({ path }) => {
const [errorState, errorDispatch] = useError();
const { error } = errorState;
return (
<>
{error ? (
<Redirect to="/" />
) : (
<>
<Redirect from={path} to={path + "/dashboard"} />
<Route to={path + "/dashboard"} component={Dashboard} />
</>
)}
</>
);
};
export default ProtectedRouteCustom;
You can see the sandbox here for full working code.
Guidelines on how to use the sandbox code
Environment for No. 1:-
uncomment/enable:
import ProtectedRoute from "./comps/routes/ProtectedRoute";
<ProtectedRoute exact path="/demo/dashboard" component={Dashboard} />
comment/disable:
import ProtectedRouteCondition from "./comps/routes/ProtectedRouteCondition";
<ProtectedRouteCondition path="/demo" />
Environment for No. 2: run as it is
Generally all coder using your methods. If you checking React in Facebook Group. You can find correct answer ı think.

React private route is not redirecting

I got headache trying to figure out why private route is not redirecting to the path I've setting up. I think a miss something but I don't know what.
Did someone help me figure out ?
here is my private route component :
import React, {useContext} from 'react';
import {Route, Redirect} from 'react-router-dom';
import AuthContext from '../../context/auth/Authcontext';
const PrivateRoute = ({component: Component, ...rest}) => {
const authContext = useContext(AuthContext);
const {isAuthenticated, loading} = authContext;
return (
<Route
{...rest}
render={props =>
!isAuthenticated && !loading ? (
<Redirect to='/pagelist' />
) : (
<Component {...props} />
)
}
/>
);
};
export default PrivateRoute;
I'm using context to handle all the authentification part.What I'm trying to do is if you are not logging you can't access the the dashboard.
here is my App.js :
import React, {Fragment, Component} from 'react';
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
import Navbar from './components/layouts/Navbar';
import Dashboard from './components/pages/Dashboard';
import Register from './components/auth/Register';
import Login from './components/auth/Login';
import PrivateRoute from './components/routing/PrivateRoute';
import AuthState from './context/auth/AuthState';
import AlertState from './context/alert/AlertState';
import setAuthToken from './utils/setAuthToken';
if (localStorage.token) {
setAuthToken(localStorage.token);
}
const App = () => {
return (
<AuthState>
<AlertState>
<Router>
<Fragment>
<Navbar />
<div className='container'>
<Alerts />
<Switch>
<PrivateRoute exact path='/' component={Dashboard} />
<Route exact path='/register' component={Register} />
<Route exact path='/login' component={Login} />
</Switch>
</div>
</Fragment>
</Router>
</AlertState>
</AuthState>
);
};
export default App;
I think I did everything correctly because when I'm watching in my console, current user are not authenticated but the dashboard is still accessible. Did I miss something ?

Can't get the value passed in any way to the entry index.js

I am learning redux with react and hit a mental roadblock, I have the following entry index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Redirect, Route} from 'react-router-dom';
import {Provider} from 'react-redux';
import store from './store';
import Bootstrap from 'bootstrap/dist/css/bootstrap.css';
import Main from './components/front/Main';
import Login from './components/front/Login';
import Home from './components/front/Home';
import Register from './components/front/Register';
import Forgot from './components/front/Forgot';
import Verify from './components/front/Verify';
import Dashboard from './components/back/Dashboard';
import './css/app.css';
import Auth from './components/Auth';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
Auth.isLogged ? ( //<-need to get props here
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/',
state: { from: props.location }
}}/>
)
)}/>
)
const App = () => (<BrowserRouter>
<div>
{/* PUBLIC */}
<Route path={'/'} exact component={Home}/>
<Route path={'/login'} component={Login}/>
<Route path={'/register'} component={Register}/>
<Route path={'/forgot'} component={Forgot}/>
<Route path={'/verify'} component={Verify}/>
{/* PRIVATE */}
<PrivateRoute path="/dashboard" component={Dashboard}/>
</div>
</BrowserRouter>);
ReactDOM.render(<Provider store={store}><App/></Provider>, document.getElementById('root'));
Overall redux works, the only problem is in this file, as I am not exporting anything, I can't get connect to work.
Ideally Auth.isLogged should be either true or false based on isAuthenticated prop, but I can't get the value passed in any way to the entry index.js
import React from 'react';
import { connect } from 'react-redux';
#connect((store) => {
return {isAuthenticated: store.auth.isAuthenticated, authenticating: store.auth.authenticating};
})
export default class Auth extends React.Component {
//Can't get anything from here to index.js
}
Please any help would be greatly appreciated!
If I understand correctly, you’re trying to provide context of store.auth.isAuthenticated to PrivateRoute.
If you declare App in its' own file you can connect it to the store and then pass the value of isAutenticated to your PrivateRoute component
//App.js
import React, { Component } from 'react'
import { withRouter, Switch, Route } from 'react-router-dom'
import { connect } from 'react-redux'
import PrivateRoute from 'components/PrivateRoute'
#withRouter
#connect(store => ({
isAuthenticated: store.auth.isAuthenticated,
authenticating: store.auth.authenticating
}))
class App extends Component {
render() {
const { isAuthenticated } = this.props
return (
<span>
{/* PUBLIC */}
{ !isAuthenticated &&
<Switch>
<Route path={'/'} exact component={Home}/>
<Route path={'/login'} component={Login}/>
<Route path={'/register'} component={Register}/>
<Route path={'/forgot'} component={Forgot}/>
<Route path={'/verify'} component={Verify}/>
</Switch>
}
{/* PRIVATE */}
{ isAuthenticated &&
<PrivateRoute
isAuthenticated={isAuthenticated}
path="/dashboard"
component={Dashboard}
/>
}
</span>
)
}
}
export default App
For readability sake you could make this change in index.js
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
)
You may or may not need to wrap connect(App) inside of withRouter, but the way I was handling my routing made this part necessary.

Resources