How to redirect user back to initially-requested page after authentication with React-Router v6? - reactjs

I have a situation where, if a user isn't authenticated, they get redirected to the login page.
Once the user logs in, they get sent back to the main page.
This is a pretty typical situation with React, but I'm wondering how I can redirect the user back to a certain page after they authenticate.
Suppose the user tries to access /app/topics, which would be a private route. They would get redirected to /, where they have to authenticate. Afterwards, they get redirected to /app/about once they authenticated.
How can I ensure that the user gets redirected back to /app/topics instead?
The About component would look something like,
const About = ({ user }) => {
const navigate = useNavigate();
React.useEffect(() => {
if (!user) navigate("/");
}, [user]);
return (
<div>
<h2>About</h2>
</div>
);
};
export default About;
And Home (or the 'login page') would look like this,
const Home = ({ user, setUser }) => {
const navigate = useNavigate();
React.useEffect(() => {
if (user) navigate("/app/about");
}, [user]);
return (
<div>
<h2>Login</h2>
<input />
<button onClick={() => setUser(1)}>login</button>
</div>
);
};
export default Home;
I'm aware the this line,
if (user) navigate("/app/about");
Is why the user gets redirected to About upon authenticating, but I'm wondering how I can change this up, so that the user is redirected back to Topics.
I've tried a combination of different approached. The most promising thing that I've tried was saving the requested uri into my Redux state. This cause issue with my existing code, however.
I've also tried saving state with Navigate or useNavigate.
If I recall correctly, React-Router v5 had a redirect prop or something of the sort, that redirected the user back to a certain page.

I would just fallback to good old query parametr in url, just upon redirecting to login page, put query param, from or something in url, and upon successfull login do the redirect, this has the huge advatage that if you take him to different page or for some reason he has to reload page, he keeps this info.

React router v6 documentation provides an exemple that answers you requirements, here is their sandbox.
They use the from property of location's state:
function LoginPage() {
let navigate = useNavigate();
let location = useLocation();
let auth = useAuth();
let from = location.state?.from?.pathname || "/";
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
let formData = new FormData(event.currentTarget);
let username = formData.get("username") as string;
auth.signin(username, () => {
// Send them back to the page they tried to visit when they were
// redirected to the login page. Use { replace: true } so we don't create
// another entry in the history stack for the login page. This means that
// when they get to the protected page and click the back button, they
// won't end up back on the login page, which is also really nice for the
// user experience.
navigate(from, { replace: true });
});
}

Related

Navigate the user to another URL using useNavigate() of react-router-dom-v6 [duplicate]

This question already has answers here:
React Router Dom, useNavigate not redirecting to the right url path
(2 answers)
Closed yesterday.
I am working on a React 18 application and I am trying to navigate the guest user to the shipping page (URL: localhost:3000/shipping) from the cart page by clicking the Proceed to Checkout button. On clicking the button, the user should be first re-directed to the sign in page where he should sign in (on the SigninScreen page) and then proceed to the shipping page.
The current url when clicking the Proceed to Checkout button is (URL: localhost:3000/signin?redirect=shipping)
On clicking the SignIn button on the SignInScreen page I do a redirect in the useEffect() hook:
import { useSearchParams, useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
const userSignin = useSelector((state) => state.userSignin);
const { userInfo, loading, error } = userSignin;
const [searchParams] = useSearchParams();
const redirectUrl = searchParams.get('redirect') || '/';
const navigate = useNavigate();
useEffect(() => {
// If the user is logged in, the user should redirected to the shipping page.
if (userInfo) {
navigate(redirectUrl, {replace: true});
}
}, [userInfo, redirectUrl, navigate]);
Even after using {replace: true} the user does not get navigated to localhost:3000/shipping, instead it gets redirected to localhost:3000/signin/shipping
Could someone please guide me on what am I doing wrong?
Tried: Even after using {replace: true} the user does not get navigated to localhost:3000/shipping, instead it gets redirected to localhost:3000/signin/shipping
Expectation: The user should navigate to localhost:3000/shipping from localhost:3000/signin?redirect=shipping on signing the user in on the sign in screen by clicking the Sign In button
URLs which do not start with a / are treated as relative links to the current route. You are navigating the user to shipping when it should be /shipping.
Here’s one way to add the starting slash:
const redirectUrl = '/' + (searchParams.get('redirect') || '');

How do I avoid duplicate action dispatches caused by to a delay in redux store update/propagation of redux state to connected components?

I'm working on a React app that uses React-Router and Redux.
This app has certain routes that require authentication, in this case I'm redirecting the user to a login page as follows.
return <Redirect
to={{
pathname: '/login',
search: `?ret=${encodeURIComponent(location.pathname)}`
}}
The login page is rendered using a Login component, which is connected to the redux store and checks a boolean isSignedIn. When isSignedIn === true, I redirect the user to the path specified in the ret query param. The relevant code is below.
const Login = (props) => {
if (!props.isSignedIn) {
// show a message and CTA asking the user to log in
}
const queryParams = qs.parse(props.location.search, { ignoreQueryPrefix: true });
const ret = queryParams.ret || '/';
return <Redirect to={ret} />
}
I'm using google oAuth2 in the app, and after the user signs in via Google, I dispatch a SIGN_IN action which updates isSignedIn to true. The oAuth flow is handled by a GoogleAuth component, which checks whether a user is signed in each time a page is rendered, and also handles the sign in/sign out click events. This component renders on every page of the app.
class GoogleAuth extends React.Component {
componentDidMount() {
window.gapi.load('client:auth2', () => {
window.gapi.client.init({
clientId: 'someclientid',
scope: 'email'
}).then(() => {
this.auth = window.gapi.auth2.getAuthInstance();
// initial check to see if user is signed in
this.onAuthChange(this.auth.isSignedIn.get());
// listens for change in google oAuth status
this.auth.isSignedIn.listen(this.onAuthChange);
})
});
};
onAuthChange = async (isGoogleSignedIn) => {
if (!this.props.isSignedIn && isGoogleSignedIn){
await this.props.signIn(this.auth.currentUser.get().getId());
} else if (this.props.isSignedIn && !isGoogleSignedIn) {
await this.props.signOut();
}
}
handleSignIn = () => {
this.auth.signIn();
}
handleSignOut = () => {
this.auth.signOut();
}
The issue I'm facing is that the SIGN_IN action (dispatched by calling this.props.signIn()) is getting called multiple times when I log in from the '/login' page, and get redirected.
It appears that the redirect occurs before the redux store has properly updated the value of isSignedIn and this results in a duplicate SIGN_IN action dispatch.
How can I prevent this? I considered adding a short delay before the redirect, but I'm not sure that's the right way.
EDIT:
I found out that this is happening because I'm rendering the GoogleAuth component twice on the login page (once in the header, and once on the page itself). This resulted in the action getting dispatched twice. Both GoogleAuth components detected the change in authentication status, and as a result both dispatched a SIGN_IN action. There was no delay in propagation of redux store data to the connected component, at least in this scenario.
Ok, figured this out.
I'm rendering the GoogleAuth component twice on the login page - once in the header, and once within the main content. That's why the action was getting dispatched twice.
Both GoogleAuth components detected the change in authentication status, and as a result both dispatched a SIGN_IN action.

PrivateRoute not redirecting to specified route when there are no user logged in

I am using firebase to handle the register login and logout. I made my "/" to a private route so that when the currentUser logged in, it will allow the user to visit that route but if there are no currentUser logged in, it will redirect to "/login route.
However it still tries to render the "/" even if there are no logged in users or after the user click log out resulting to errors
Here is the error
Here is my PrivateRoute.js
Here is the logout button handler
I have done something similar using the following example hopefully it solves your issue, I think how you have done it is slightly complex I prefer to use history.push to redirect.
I find the below easier to read and works in my projects.
const history = useHistory();
const currentUser = useAuth();
useEffect(() => {
if (!currentUser) {
history.push('/login')
}
}, [currentUser]);
return (
<Route
{...props}
component={props.component} />
);

How can I stay the user in the same page?

Every time I reload the my account page, it will go to the log in page for a while and will directed to the Logged in Homepage. How can I stay on the same even after refreshing the page?
I'm just practicing reactjs and I think this is the code that's causing this redirecting to log-in then to home
//if the currentUser is signed in in the application
export const getCurrentUser = () => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
unsubscribe();
resolve(userAuth); //this tell us if the user is signed in with the application or not
}, reject);
})
};
.....
import {useEffect} from 'react';
import { useSelector } from 'react-redux';
const mapState = ({ user }) => ({
currentUser: user.currentUser
});
//custom hook
const useAuth = props => {
//get that value, if the current user is null, meaning the user is not logged in
// if they want to access the page, they need to be redirected in a way to log in
const { currentUser } = useSelector(mapState);
useEffect(() => {
//checks if the current user is null
if(!currentUser){
//redirect the user to the log in page
//we have access to history because of withRoute in withAuth.js
props.history.push('/login');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},[currentUser]); //whenever currentUser changes, it will run this code
return currentUser;
};
export default useAuth;
You can make use of local storage as previously mentioned in the comments:
When user logs in
localStorage.setItem('currentUserLogged', true);
And before if(!currentUser)
var currentUser = localStorage.getItem('currentUserLogged');
Please have a look into the following example
Otherwise I recommend you to take a look into Redux Subscribers where you can persist states like so:
store.subscribe(() => {
// store state
})
There are two ways through which you can authenticate your application by using local storage.
The first one is :
set a token value in local storage at the time of logging into your application
localStorage.setItem("auth_token", "any random generated token or any value");
you can use the componentDidMount() method. This method runs on the mounting of any component. you can check here if the value stored in local storage is present or not if it is present it means the user is logged in and can access your page and if not you can redirect the user to the login page.
componentDidMount = () => { if(!localStorage.getItem("auth_token")){ // redirect to login page } }
The second option to authenticate your application by making guards. You can create auth-guards and integrate those guards on your routes. Those guards will check the requirement before rendering each route. It will make your code clean and you do not need to put auth check on every component like the first option.
There are many other ways too for eg. if you are using redux you can use Persist storage or redux store for storing the value but more secure and easy to use.

Redirection after successful login operation

I am facing a problem while I am trying to redirecting user to home page after successful login. I am using this example in github.
https://github.com/cornflourblue/react-redux-registration-login-example
In this file, "src/LoginPage/LoginPage.jsx", There is such a part which sends login request in a POST message but I do not see any redirection to home page, just login page. Here is the part:
handleSubmit(e) {
e.preventDefault();
this.setState({ submitted: true });
const { username, password } = this.state;
const { dispatch } = this.props;
if (username && password) {
dispatch(userActions.login(username, password));
}
}
When I look at the network activity, I see that I receive successful message from response of server. Do I need to add something that does redirection?
Yes you need to add Browser Routing to your project and after a successful login you should redirect to page which you want. For example to="/Home". At first you need something like isAuth in your redux reducer. After login submit, if it is successful set this to true and in your LoginPage with mapStateToProps get this isAuth value. Also in your render() section use this before your other jsx code ->
render() {
let redirect = null;
if(isAuth == true){
redirect = <Redirect to="/Home" />;
}
return (
{redirect}
)
}
I found the reason of problem. If response is successful, something has to be set into localStorage. In private route, it checks whether that thing exists in localStorage. If not, it does not do anything but if it has, it operates the component to be redirected. I forgot to edit this part. That's why, it did not do redirection.

Resources