I'm working on an app with a login page and the rest of the pages of the app (should be logged in to view). I'm using react-boilerplate
. From this example
, I edited my asyncInjectors.js
file to have redirectToLogin
and redirectToDashboard
methods:
//asyncInjectors.js
export function redirectToLogin(store) {
return (nextState, replaceState) => {
const isAuthenticated = store.getState().get('app').get('isAuthenticated');
if (!isAuthenticated) {
replaceState({
pathname: '/login',
state: {
nextPathname: nextState.location.pathname,
},
});
}
};
}
export function redirectToDashboard(store) {
return (nextState, replaceState) => {
const isAuthenticated = store.getState().get('app').get('isAuthenticated');
if (isAuthenticated) {
replaceState('/');
}
}
}
Then I just set the redirectToLogin
as the onEnter
of the pages and redirectToDashboard
for the login page.
It works fine but when the page is refreshed (F5
) when logged in, the login page renders briefly and then renders the actual page. The login page just dispatches an authenticate
action in componentWillMount
and then redirects in componentDidUpdate
:
//login.js
componentWillMount() {
this.props.dispatch(authenticate());
}
componentDidUpdate(prevProps, prevState) {
if (this.props.isAuthenticated) {
const nextPathname = prevProps.location.state ? prevProps.location.state.nextPathname : '/';
browserHistory.push(nextPathname);
}
}
The container for the pages also has the same componentWillMount
code. Not sure if it's because of the sagas but here's the code:
//sagas.js
export function* login({ user, password }) {
try {
const token = yield call(app.authenticate, {
strategy: 'local',
user,
password,
});
return token;
} catch (error) {
return onError(error);
}
}
// For page refresh after logging in
export function* authenticate() {
try {
const token = yield call(app.authenticate);
return token;
} catch (error) {
return onError(error);
}
}
export function* logout() {
try {
const response = yield call(app.logout);
return response;
} catch (error) {
return onError(error);
}
}
export function* loginFlow() {
while (true) {
const request = yield take(LOGIN_REQUEST);
const winner = yield race({
auth: call(login, request.data),
logout: take(LOGOUT_REQUEST),
});
if (winner.auth && winner.auth.accessToken) {
yield put(actions.setAuthState(true));
}
}
}
export function* logoutFlow() {
while (true) {
yield take(LOGOUT_REQUEST);
yield put(actions.setAuthState(false));
yield call(logout);
browserHistory.push('/login');
}
}
export function* authenticateFlow() {
while (true) {
yield take(AUTHENTICATE);
const response = yield call(authenticate);
if (response && response.accessToken) {
yield put(actions.setAuthState(true));
}
}
}
export default [
loginFlow,
logoutFlow,
authenticateFlow,
];
How do I get rid of the flashing login page?
EDIT: When I tried gouroujo's answer, I couldn't logout.
//asyncInjectors.js
import jwtDecode from 'jwt-decode';
export function redirectToLogin(store) {
return (nextState, replaceState, callback) => {
const token = localStorage.token;
if (token) {
const jwt = jwtDecode(token);
if (jwt.exp <= (new Date().getTime() / 1000)) {
store.dispatch(actions.setAuthState(false));
replaceState({
pathname: '/login',
state: {
nextPathname: nextState.location.pathname,
},
});
}
}
store.dispatch(actions.setAuthState(true));
callback();
};
}
When I hit refresh, the login page
doesn't show but now I can't log out.
You have two way to avoid flashing the login page on initial render : make your authenticate function synced or wait the answer with a loading page.
1- Check if your token is present and valid (expiration date) client-side to choose if you have to redirect the user to the login or the dashboard page first. When the answer come back from your server you correct your initial guess but in the vaste majority you won't need to.
user landing ->
To check the token client-side you have to check the local storage. For example:
class App extends Component {
requireAuth = (nextState, replace) => {
if (!localStorage.token) {
replace({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
})
}
}
render() {
return (
<Router history={browserHistory}>
<Route path="/login" component={LoginPage} />
<Route
path="/"
component={AppLayout}
onEnter={this.requireAuth}
> ... </Route>
)
}
}
If you use a token with a relatively short expiration date, you will also have to check the expiration date, thus decode the token.
try {
const jwt = JWTdecode(token);
if (moment().isBefore(moment.unix(jwt.exp))) {
return nextState;
} else {
replace({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
})
}
} catch (e) {
replace({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
})
}
2- Show a loading screen before you receive the answer from the server. add some Css transition effect on opacity to avoid the "flash"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With