So I have my protected routes enclosed in a HOC that first checks if the person is authenticated by seeing if they have a token. If they have a token, it is sent to the server to see if it is valid. If it is valid, it is refreshed (if it is < 15 minutes old). During this process the state of PERMIT_RENDER
is set true if the token is valid allowing the component to render if it is; if not, it is just a loading screen until the server responds. Hopefully this is clear in the below code.
Anyway, with my current setup, when you first login it brings you to /home
which is a protected route. You can click on links in the .nav
to go between other protected routes. The problem is permitRender
work properly when you first login and shows the loading until the server responds and brings you to /home
. At this point it seems like permitRender
should be setback to false
so that when you click on a link to a protected route, it doesn't load until the server responds and the user just gets the loading screen until then. However, permitRender
once true
stays that way unless you refresh the screen. In addition, it automatically navigates to the other protected route before receiving the server response.
Basically, what is the correct way to set permitRender
back to false
after mounting or updating so that my loading component is triggered every time someone navigates to a protected route?
Some things that seemed to intuitive to me (mind you not knowing React and hwo to use setState
that well) in componentDidMount()
, componentDidUpdate()
, componentDidMount(...state)
, etc.:
this.setState(this.state.auth.permitRender = false);
Which results in cannot read property 'auth' of null
.
// ./helpers/require_auth.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { authRequired, refreshToken } from '../actions/authentication';
import Loading from './loading';
// This HOC not only protects certain routes by checking for existence of a token,
// but if the token exists it make sure it is current
// if it is current, it will refresh the token
export default function(ComposedComponent) {
class AuthenticationRequired extends Component {
// Check if the user has a token when protected route component is about to mount
// Route them to / if they do not
componentWillMount() {
if (!sessionStorage.getItem('token')) {
this.props.authRequired();
} else {
this.props.refreshToken();
}
}
// If the component will be updated then also check if user is authorized
componentWillUpdate() {
if (!sessionStorage.getItem('token')) {
this.props.authRequired();
} else {
this.props.refreshToken();
}
}
// Render the component if passing these checks
render() {
if (this.props.permitRender) {
return <ComposedComponent {...this.props} />
} else if (!this.props.permitRender) {
return <Loading />
}
}
}
//
function mapStateToProps(state) {
return {
permitRender: state.auth.permitRender
};
}
// Conenct to the authRequired action
return connect(mapStateToProps, { authRequired, refreshToken })(AuthenticationRequired);
}
// ./actions/authentication
import axios from 'axios';
import { push } from 'react-router-redux';
import { ROOT_URL } from '../../config/config.json';
// Establish the different types
export const AUTH_USER = 'auth_user';
export const UNAUTH_USER = 'unauth_user';
export const PERMIT_RENDER = 'permit_render';
// Refresh the token based on the users activity
export function refreshToken() {
return function(dispatch) {
axios
.post(
`${ROOT_URL}/api/auth/refresh/`,
{ 'token': sessionStorage.getItem('token') },
{ headers: {
'Content-Type': 'application/json',
}
})
.then(response => {
sessionStorage.setItem('token', response.data.token);
dispatch({ type: AUTH_USER });
dispatch({ type: PERMIT_RENDER });
})
.catch(error => {
sessionStorage.clear();
dispatch({ type: UNAUTH_USER });
dispatch(push('/'));
dispatch(authError('This session has expired. Please login to continue browsing.'));
});
}
}
// ./reducers/authentication.js
import {
AUTH_USER,
UNAUTH_USER,
PERMIT_RENDER,
} from '../actions/authentication';
export default function(state = {}, action) {
switch(action.type) {
case AUTH_USER:
return { ...state, error: '', authenticated: true };
case UNAUTH_USER:
return { ...state, authenticated: false };
case PERMIT_RENDER:
return { ...state, permitRender: true };
default:
return state;
}
return state;
}
// ./reducers/index.js
import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form';
import { routerReducer } from 'react-router-redux';
import authReducer from './authentication';
import helpReducer from './help';
const rootReducer = combineReducers({
form,
router: routerReducer,
auth: authReducer,
help: helpReducer
});
export default rootReducer;