I'm doing user login and logout and after login or logout i am just changing pathname to homepage using this.props.history.push({pathname: '/', state: { url: this.props.location.pathname }});. Now when i redirect to that page the data is not updating, i have refresh the page to update the data. I am calling below code to update the data.
componentDidMount(){
let data = AuthService.fetchUserObj();
console.log(data)
this.setState({user: data})
}
Please assist me how to automatically update user data to state once pathname changed after login.
It's just an assumption but maybe fetchUser is an asynchronous method. If it's so and method returns a promise then it has to be something like:
componentDidMount(){
AuthService.fetchUserObj().then(data => {
this.setState({user: data});
}
}
Related
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.
After a lot of searching for several hours, I have the following code to redirect from a user profile page if not logged in.
NOTE: Simply showing a not authorized page is easy but its the redirect thats messing things up.
The code does the job of redirecting when user is not logged in.
const Dashboard = () => {
const [user, { mutate }] = useCurrentUser();
const router = useRouter();
useEffect(() => {
// redirect to login if user is not authenticated
if (!user) router.push('/login');
}, [user]);
...
The problem is when a user is logged in and directly goes to /user/dashboard route, for a split second, user is undefined may be so it redirects to login. When it gets to login, it finds that user is authenticated so redirects to home page because I am redirecting a logged in user to home page.
How to prevent that split second of "not a user" status when page is first loading?
I tried -
getInitialProps
getServerSideProps - Cant use router because next router can only be used on client side
componentDidMount - UseEffectI tried above is the equivalent correct?
Edit: Based on answer below, I tried this but still directly takes user to login first. I am using react cookies and I do see loggedIn cookie as true when user is logged in and its not set when user is not logged in.
Dashboard.getInitialProps = ({ req, res }) => {
console.log(req.headers.cookie)
var get_cookies = function(request) {
var cookies = {};
request.headers && request.headers.cookie.split(';').forEach(function(cookie) {
var parts = cookie.match(/(.*?)=(.*)$/)
cookies[ parts[1].trim() ] = (parts[2] || '').trim();
});
return cookies;
};
//console.log(get_cookies(req)['loggedIn']);
if (get_cookies(req)['loggedIn'] == true) {
console.log("entered logged in")
return {loggedIn: true};
}
else {
console.log("entered not logged in")// can see this on server console log
// User is not logged in, redirect.
if (res) {
// We're on the server.
res.writeHead(301, { Location: '/login' });
res.end();
} else {
// We're on the client.
Router.push('/login');
}
}
}
You can implement redirect when not authenticated in getServerSideProps
Below example is based on JWT Authentication with cookies.
export const getServerSideProps = async (ctx) => {
const cookie = ctx.req.headers.cookie;
const config = {
headers: {
cookie: cookie ?? null
}
}
let res;
try {
// your isAuthenticated check
const res = await axios('url', config);
return { props: { user: res.data } };
} catch (err) {
console.error(err);
ctx.res.writeHead(302, {
Location: 'redirectUrl'
})
ctx.res.end();
return;
return { props: { user: null } };
}
}
You should be able to use getInitialProps to redirect. You just need to check whether you're on the server or the client and use the proper redirect method. You can't use hooks in getInitialProps so your useCurrentUser approach won't work and you'll need some other way to check whether the user is authed. I don't know anything about the structure of your application, but it's probably just some kind of request to wherever you're storing the session.
import Router from 'next/router';
const Dashboard = (props) => {
// props.user is guaranteed to be available here...
};
Dashboard.getInitialProps = async ({ res }) => {
// Check authentication.
// Await the response so that the redirect doesn't happen prematurely.
const user = await ...
// User is logged in, return the data you need for the page.
if (user) {
return { user };
}
// User is not logged in, redirect.
if (res) {
// We're on the server.
// Make the redirect temporary so it doesn't get cached.
res.writeHead(307, { Location: '/login' });
res.end();
} else {
// We're on the client.
Router.push('/login');
}
};
After many hours of struggle, there was one number that was breaking this.
Instead of
res.writeHead(301, { Location: '/login' });
I used
res.writeHead(307, { Location: '/login' });
and it worked.
301 is a permanent redirect so if we use that, when the user logs in, the browser still holds the redirect cache.
From next js docs
Next.js allows you to specify whether the redirect is permanent or not with the permanent field. This is required unless you need to specify the statusCode manually
When permanent is set to true we use a status code of 308 and also set a Refresh header for backwards compatibility with IE11.
When permanent is set to false we use a status code of 307 which is not cached by browsers and signifies the redirect is temporary.
Next.js permits the following status codes:
-301 Moved `Permanently`
-302 Found
-303 See Other
-307 `Temporary` Redirect
-308 Permanent Redirect
I have a function running using a setInterval timer. Its purpose is to ask the server if the login/sessionID I have in local storage is still valid. If the session has expired, I want to give an alert and route to the Login page.
I would normally use something like this.props.history.push("/login") to accomplish the redirect. But this cannot be a Component; being run by setInterval, it must be a function. The React-Router history.push option is not available outside of Components, AFAIK. What would be my best alternative?
Here is the setInterval code that happens at Login:
sessionStorage.setItem('mycha_sessionID',returnData.data.sessionID)
let intervalID = setInterval(CheckSession,1*30*1000)
sessionStorage.setItem('intervalID',intervalID)
And the function code that will run every interval:
import {axiosGet} from "./AxiosCalls";
import {ClearSession} from "./ClearSession";
export function CheckSession() {
// Call Wordpress to see if the current session ID is still valid
if (sessionStorage.getItem('mycha_sessionID')) {
axiosGet(sessionStorage.getItem('mycha_base_url')+"/wp-json/mycha/check-session")
.then(res => {
if ((false === res.data) || ("" === res.data)) {
ClearSession()
alert("Your login session has expired. Please log in again before continuing.")
// Redirect to Login here
}
})
.catch(err => alert(err));
}
return
}
I may be misunderstanding, but why not just call
window.location.href = 'https://www.my-site.com/login'
I have got the url to change to the url I want it to, but the only way I can get it to work is by refreshing the page and then it goes to the url.
An example is lets say I am on localhost:3000/signin and when I sign in I want the user to be redirected to the posts page on localhost:3000/posts. When I click the button I get localhost:3000/posts but the page just stays on the signin page. I have to hit refresh for it to go to that URL.
**********
EDIT: I also noticed that when I hit back or forward in the browser that it isn't rendering till I hit refresh also. So this could be some other issue? I am using react-router-v4.
Here is the code I have so far:
This is the on submit function being called when the button is clicked:
onSubmit({email, password}) {
this.props.signinUser({email, password}, () => {
this.props.history.push('/posts');
});
}
this is the action signinUser:
export function signinUser({email, password}, cb) {
return function(dispatch) {
axios.post(`${ROOT_URL}/signin`, {email, password})
.then((response) => {
dispatch({type: AUTH_USER});
console.log(response);
localStorage.setItem('token', response.data.token);
cb();
})
.catch(() => {
dispatch(authError('bad login info'));
})
}
}
can you try this, this should work.
this.history.pushState(null, '/posts');
if you are using browserHistory
this.context.router.push('/posts'); or browserHistory.push('/login');
One way to do it is that somewhere in state you have item "isLoggedin" or something like that. If that item is false, you you render login route normally with edit boxes to enter user info. when isLoggedIn in state changes to true, you render your login route to something like this:
<Redirect to="/posts"/>
and off you go!
An example implementation is an action that sends user registration information to the action; then redirects the user to user's profile page on creation.
Currently I am setting state that is managed on the user registration page. When the state says the user is created, and passes the user id, they are redirected.
It would be 'simpler' to simply redirect from the action itself though via a browserhistory push.
I would love to know if this is anti-pattern, or an acceptable method
Sample redux-thunk's action psuedo-code:
export const apiNewSomething = (name, email, other) => {
return dispatch => {
return fetch(`//server/v1/something`, { method: "post", body: {name, email, other} })
.then((res) => {
dispatch(addSomethingToReduxAction(res))
browserHistory.push(`//server/something/${res.id}`)
}, (err) => {
console.log('danger will robinson!')
});
}
}