Prevent user from going back on browser if already logged in (react) - javascript

I would like to refresh the current page (home) when the user tries to go back via browser, after logged in.
What's the best way to solve this? Any suggestions?
I was trying to do something like this inside index.tsx:
if (id) {
const rollback = history.goBack();
if (rollback) {
history.push('/');
}
}
Obs: In this case, '/' is my home page, and i can't apply the logic above because "An expression of type 'void' cannot be tested for truthiness".
Sorry for anything i'm still new at react and trying to learn.
Don't know if i could do something inside my router, here it is anyway:
import React, { Suspense, lazy } from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import history from '../utils/history';
import LoadingPage from '../components/organisms/LoadingPage';
const DownloadExams = lazy(() => import('../pages/private/DownloadExams'));
const Home = lazy(() => import('../pages/private/Home'));
const ProfileSelector = lazy(() => import('../pages/private/ProfileSelector'));
const AppRoutes = () => {
return (
<Router history={history}>
<Suspense fallback={<LoadingPage />}>
<Switch>
<Route exact path={'/'} component={Home} />
<Route exact path={'/baixar-exames'} component={DownloadExams} />
<Route exact path={'/profile'} component={ProfileSelector} />
</Switch>
</Suspense>
</Router>
);
};
export default AppRoutes;
Any suggestions?
Thanks!

User logIn time you can store a token or flag and store it in localStorage. After that, you can check if the user login so redirects to the page. You can also create some HOC for the same.
Example :
import React from "react";
import { Route, Redirect } from "react-router-dom";
export const ProtectedRoute = ({ component: Component, ...rest }) => {
const isLoggedIn = localStorage.getItem("token");
return (
<Route
{...rest}
render={(props) => {
if (isLoggedIn) {
return <Component {...props} />;
} else {
return (
<Redirect
to={{
pathname: "/",
state: {
from: props.location,
},
}}
/>
);
}
}}
/>
);
};
Example Usage :
<ProtectedRoute path="/home" exact component={Home} />
This will redirect the user to /home after login.

Related

Nested routing using protected routes is not working properly

earlier I posted a similar question: Method for checking admin is redirecting me to main page when trying to login
I tried to implement the protected route, inside a protectRoute.tsx:
import { Navigate, Outlet } from "react-router";
import { RootStateOrAny, useSelector } from "react-redux";
interface ProtectRouteProps {}
export default function ProtectRoute(props: ProtectRouteProps) {
const userSignin = useSelector((state: RootStateOrAny) => state.userSignin);
const { userInfo } = userSignin;
return userInfo?.user?.isAdmin ? <Outlet /> : <Navigate to='/' />;
}
I don't really know what ProtectRouteProps is and what I should put in it. Also in the routing part I did like he told me:
<Route path='/createItem' element={<ProtectRoute />} />
<Route path='/createItem' element={<CreateItem />} />
The problem now is that can't access CreateItem because is going on the ProtectRoute page that is an empty one. What should i do?
I don't really know what ProtectRouteProps is and what I should put in it.
There are no props. This is clear by the usage:
<Route path='/createItem' element={<ProtectRoute />} />
No props are passed to ProtectRoute. You can drop the props object:
import { Navigate, Outlet } from "react-router";
import { RootStateOrAny, useSelector } from "react-redux";
export default function ProtectRoute() {
const userSignin = useSelector((state: RootStateOrAny) => state.userSignin);
const { userInfo } = userSignin;
return userInfo?.user?.isAdmin ? <Outlet /> : <Navigate to='/' replace />;
}
The problem now is that can't access CreateItem because is going on
the ProtectRoute page that is an empty one. What should i do?
"Auth" routes are what are called layout routes. They apply some logic, perhaps some styled layout CSS, and render an Outlet for nested Route components to be rendered into. The nested Route components use the path prop for route matching.
Example:
<Route element={<ProtectRoute />}>
<Route path='/createItem' element={<CreateItem />} />
... other protected routes ...
</Route>
<Route exact path='/Login' element={<Login name="Login Page"></Login>}></Route>
<Route element={<Protected/>}>
<Route exact path='/' element={<Home/> }></Route>
<Route exact path='/Content' element={<Content />}></Route>
</Route>
<Route path='*' element={<Login/>} ></Route>
</Routes>
Create Protected.js
import { Navigate, Outlet } from 'react-router-dom';
const useAuth = ()=>{
if(localStorage.getItem("isLogged")){
const user = {loggedIN :true};
return user && user.loggedIN
}else{
const user = {loggedIN :false};
return user && user.loggedIN
}
}
const Protected = () => {
const isAuth = useAuth();
return isAuth ? <Outlet/>:<Navigate to={"/login"}/>
}
export default Protected

Issue with history.push

I'm working on react Disney+ clone etc and I was trying to do something like: if user isnt authorized then show login page but if authorized then show content. I used useHistory for this. And it works for a second, it just starts to download login page (background image is loading, but text of login page is visible) and then it disappears and content page is shown. Url changes for a second too.
App.js
function App() {
return (
<div className="App">
<Router>
<Header />
<Switch>
<Route path="/login">
<Login/>
</Route>
<Route path="/detail/:id">
<Detail/>
</Route>
<Route path="/">
<Home/>
</Route>
</Switch>
</Router>
</div>
);
}
Header.js
import React from 'react'
import {selectUserName, selectUserPhoto, setUserLogin, setUserSignOut} from '../../features/user/userSlice' ;
import { useDispatch, useSelector } from "react-redux" ;
import { auth, provider} from "../../firebase"
import { useHistory} from 'react-router-dom';
const Header = () => {
const dispatch = useDispatch()
const userName = useSelector(selectUserName);
const userPhoto = useSelector(selectUserPhoto);
const history = useHistory();
const signIn = () => {
auth.signInWithPopup(provider)
.then((result) => {
let user = result.user;
dispatch(setUserLogin({
name: user.displayName,
email: user.email,
photo: user.photoURL
}))
history.push('/');
})
}
const signOut = () => {
auth.signOut()
.then(() => {
dispatch(setUserSignOut());
history.push('/login');
})
}
}
Based on your issue you can handle Routes in a different way. So you have routes which can only shown during unauthorised situation and some routes only shown for authorised user. For that you can have following implementation.
First you can create ProtectedRoute function.
import React from "react";
import { Redirect, Route } from "react-router-dom";
function ProtectedRoute({ component: Component, ...restOfProps }) {
const isAuthenticated = localStorage.getItem("isAuthenticated");
console.log("this", isAuthenticated);
return (
<Route
{...restOfProps}
render={(props) =>
isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
export default ProtectedRoute;
And then you can use this function in your main App where you will declare your routes with component.
import ProtectedRoute from "./component/ProtectedRoute";
function App() {
return (
<div className="App">
<BrowserRouter>
<Route path="/login" component={Login} />
<ProtectedRoute path="/protected" component={ProtectedComponent} />
</BrowserRouter>
</div>
);
}
export default App;

Default route always execute in react router

I am working on a project where I am using the strikingDash template. Here I face some issues of routing while changing the routes from URL.
auth.js
import React, { lazy, Suspense } from "react"
import { Spin } from "antd"
import { Switch, Route, Redirect } from "react-router-dom"
import AuthLayout from "../container/profile/authentication/Index"
const Login = lazy(() =>
import("../container/profile/authentication/overview/SignIn")
)
const SignUp = lazy(() =>
import("../container/profile/authentication/overview/SignUp")
)
const ForgetPassword = lazy(() =>
import("../container/profile/authentication/overview/ForgetPassword")
)
const EmailConfirmation = lazy(() =>
import("../container/profile/authentication/overview/EmailConfirmation")
)
const VerificationPage = lazy(() =>
import("../container/profile/authentication/overview/VerificationPage")
)
const NotFound = () => {
console.log("NOT FOUND")
return <Redirect to="/" />
}
const FrontendRoutes = () => {
return (
<Switch>
<Suspense
fallback={
<div className="spin">
<Spin />
</div>
}
>
<Route exact path="/verification" component={VerificationPage} />
<Route exact path="/email-confirmation" component={EmailConfirmation} />
<Route exact path="/forgetPassword" component={ForgetPassword} />
<Route exact path="/signup" component={SignUp} />
<Route exact path="/" component={Login} />
<Route component={NotFound} />
</Suspense>
</Switch>
)
}
export default AuthLayout(FrontendRoutes)
App.js
import React, { useEffect, useState } from "react";
import { hot } from "react-hot-loader/root";
import { Provider, useSelector } from "react-redux";
import { ThemeProvider } from "styled-components";
import { BrowserRouter as Router, Redirect, Route } from "react-router-dom";
import { ConfigProvider } from "antd";
import store from "./redux/store";
import Admin from "./routes/admin";
import Auth from "./routes/auth";
import "./static/css/style.css";
import config from "./config/config";
import ProtectedRoute from "./components/utilities/protectedRoute";
const { theme } = config;
const ProviderConfig = () => {
const { rtl, isLoggedIn, topMenu, darkMode } = useSelector(state => {
return {
darkMode: state.ChangeLayoutMode.data,
rtl: state.ChangeLayoutMode.rtlData,
topMenu: state.ChangeLayoutMode.topMenu,
isLoggedIn: state.Authentication.login,
};
});
const [path, setPath] = useState(window.location.pathname);
useEffect(() => {
let unmounted = false;
if (!unmounted) {
setPath(window.location.pathname);
}
// eslint-disable-next-line no-return-assign
return () => (unmounted = true);
}, [setPath]);
return (
<ConfigProvider direction={rtl ? "rtl" : "ltr"}>
<ThemeProvider theme={{ ...theme, rtl, topMenu, darkMode }}>
<Router basename={process.env.PUBLIC_URL}>
{!isLoggedIn ? <>{console.log("INSIDE PUBLIC")}<Route path="/" component={Auth} /></> : <ProtectedRoute path="/admin" component={Admin} />}
{isLoggedIn && (path === process.env.PUBLIC_URL || path === `${process.env.PUBLIC_URL}/`) && (
<Redirect to="/admin" />
)}
</Router>
</ThemeProvider>
</ConfigProvider>
);
};
function App() {
return (
<Provider store={store}>
<ProviderConfig />
</Provider>
);
}
export default hot(App);
Whenever I change the URL to another route as I described above in Frontend Routes. Then it will always print console statements like these:
INSIDE PUBLIC
NOT FOUND
INSIDE PUBLIC
NOT FOUND
Expected Behaviour: Whenever I update the URL it will render the component according to the switch case and return it back
Actual Behaviour: Whenever I update the URL it will render the component as well as the default component. I think Switch here renders multiple components, but I don't know why.
I just resolved the issue by moving the Switch Tag inside the Suspense tag in the auth.js file.
The problem should be in the order of your pages: the root path works as a collector of all the pages, you should try to add the exact keyword to the Router path. Here the reference for the differences between the different notations.
<Route exact path="/" component={Login} />

ReactJS: Global Okta Auth Helper Function

I have my React web app authenticated using Okta however if the user is not authenticated or the token expires then it will redirect the user to the /login page.
Page1.js:
import React, { useState, useEffect } from "react";
import { useOktaAuth } from "#okta/okta-react";
import { Redirect, useHistory } from "react-router-dom";
const { authState } = useOktaAuth();
const history = useHistory();
useEffect(() => {
if (!authState) {
history.push("/login");
}
});
Is there a way I can include the above into an function that I can import to each different Page[#].js instead of including the above code in each file?
Just trying to refactor and make sure my code is tidy as I go but unsure the best way to tackle this.
TIA!
You can wrap all the components that required users to be logged in inside a component and protect the routes.
Once the user logs in, set a token in localstorage or redux (you need to persist it). Specify the route to go through IsloggedIn component so that, those routes will only be accessible if user is logged in, else will be redirected to the login page
Example
import React from "react";
import { Redirect, Route } from "react-router-dom";
function IsLoggedIn({ Component, ...props }) {
const isAuthenticated = localStorage.getItem("isAuthenticated");
return (
<Route {...props}
render={(props) =>
isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
export default IsLoggedIn;
In App.js
function App(){
return(
<BrowserRouter>
<Route exact path="/login" component={Login} />
<IsLoggedIn exact path="/" component={Page1} />
</BrowserRouter>
)
}
you can use the below
private-route component
import React, { useEffect } from 'react';
import { Route, Redirect, RouteProps, useHistory } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from 'app/config/store';
import { SecureRoute, useOktaAuth } from '#okta/okta-react';
import { authenticationSuccess } from '../reducers/authentication-slice';
export const PrivateRouteComponent = ({ component: Component, ...rest }: IOwnProps) => {
//reducer
const isAuthenticated = useAppSelector(state => state.authentication.isOktaAuthenticated);
const { authState, oktaAuth } = useOktaAuth();
const dispatch = useAppDispatch();
useEffect(() => {
if (!isAuthenticated && authState && authState.isAuthenticated) {
oktaAuth.token.getUserInfo().then(info => {
//action dispatch
dispatch(authenticationSuccess({ name: info.name, email: info.email }));
});
}
}, [authState, oktaAuth]); // Update if authState changes
const renderRedirect = props => {
return isAuthenticated && <Component {...props} />;
};
return <SecureRoute {...rest} render={renderRedirect} />;
};
export default PrivateRouteComponent;
in app.js
<Security oktaAuth={oktaAuth} restoreOriginalUri={restoreOriginalUri}>
<Switch>
<Route path="/callback" component={LoginCallback} />
<PrivateRoute path="/" exact={true} component={Home} />
<PrivateRoute path="/home" exact={true} component={Home} />
</Switch>
</Security>

Problems with react router trying to redirect users

i'm working on a MERNG app, and i have a problem loging out the user.
So, i have a route, that will take you to a page where the url has /profile/userId, the userID is given by the localStorage since i'm taking that value from there, and, when i logout i remove the token from the localStorage, so, if there's no values, it should take you login page.
Now, the problem is that, when i login, and get to the /profile/userId page, and i refresh the page with f5, if i logout, it won't let me do it, the redirecting doesn't work, it only works if i don't refresh the page, and that's a big issue for my app, and it's actually weird.
Maybe it's a problem with my code, i don't know that well how to use Redirect, so if you can help me with this, you're the greatest !
So this is the code
Routes with the redirect
import React from "react";
import "./App.css";
import {
BrowserRouter as Router,
Switch,
Route,
Redirect
} from "react-router-dom";
import Auth from "./website/Auth";
import SocialMedia from "./website/SocialMedia";
import SingleUser from "./website/SingleUser";
function App() {
const logIn = JSON.parse(localStorage.getItem("token"));
return (
<Router>
<Switch>
<Route exact path="/">
{logIn ? <Redirect to={`/profile/${logIn.data.id}`} /> : <Auth />}
</Route>
<Route exact path="/socialmedia" component={SocialMedia} />
<Route exact path="/profile/:id" component={SingleUser} />
</Switch>
</Router>
);
}
export default App;
To logout
import React from "react";
import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
const SingleUser = () => {
const history = useHistory();
const dispatch = useDispatch();
const userData = JSON.parse(localStorage.getItem("token"));
const logout = () => {
dispatch({ type: "LOGOUT" });
if (!userData) {
history.push("/");
}
};
return <div onClick={logout}>Single User</div>;
};
export default SingleUser;
My reducer where i store the token in the localStorage and remove it with logout action
const reducer = (state = { authData: null }, action) => {
switch (action.type) {
case "LOGIN":
localStorage.setItem("token", JSON.stringify({ data: action.payload }));
return {
...state,
authData: action.payload
};
case "LOGOUT":
localStorage.removeItem("token");
return {
...state,
authData: null
};
default:
return state;
}
};
export default reducer;
This is simply because you are rendering the Auth component only in the "/" route.
So if you are on a different route, you are skipping the "/" route and the user route will be shown. There are different ways on how to handle it:
Always redirect
<Switch>
<Route exact path="/">
<Auth />
</Route>
{!logIn && <Route path="*"><Redirect to={`/profile/${logIn.data.id}`} /></Route>} // This route will not always be rendered if the user should login and is not on the "/" page
<Route exact path="/socialmedia" component={SocialMedia} />
<Route exact path="/profile/:id" component={SingleUser} />
</Switch>
Redirect manually for every route
const SingleUser = () => {
const history = useHistory();
const dispatch = useDispatch();
const userData = JSON.parse(localStorage.getItem("token"));
if(!userData) histroy.push("/")
...
Early return
function App() {
const logIn = JSON.parse(localStorage.getItem("token"));
if(!login) return <Auth />
return (
<Router>
<Switch>
<Route exact path="/">
<Redirect to={`/profile/${logIn.data.id}`} />
</Route>
<Route exact path="/socialmedia" component={SocialMedia} />
<Route exact path="/profile/:id" component={SingleUser} />
</Switch>
</Router>
);
}
Additionally, userData will always be defined on logout, since you are not accessing a new lcoalState but the current one, before it got removed. It still lives in your varibale.
if (!userData) { // Remove the if
history.push("/");
}
Another quite easy solution for you might be just doing this:
<Route exact path="/" render={() => (logIn ? <SingleUser /> : <Auth />)} />
This way on every rerender it will check if there is a user and if not it will redirect you directly to the Auth Component.
Hi mike from the past !
Your code it's okay, but, when you click logout, the variable on the App.js component is still the same, so, you won't get the results you're looking for, what you have to do, it's to reload the website, so te App.js component will upload the variable and will recognize that the value of variable has changed, so you will go to login/register again!
just change your code for this one, so far everything work as expected
import React from "react";
import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
const SingleUser = () => {
const history = useHistory();
const dispatch = useDispatch();
const logout = () => {
dispatch({ type: "LOGOUT" });
history.push("/");
history.go("/");
};
return <div onClick={logout}>Single User</div>;
};
export default SingleUser;

Categories

Resources