I am working on a reactjs/redux application - and using a Java Spring Boot codebase acting as an api.
I've got a login system in place - but I notice that when I refresh the page -- the authentication state is lost.
I've added redux-persist, but it doesn't appear to take any affect in stashing the logged in state and resuming the experience?
The bulk of the redux-persist is in place on my router.js --- its here where the store/provider gets set to the application.
I am unsure how and where to store the state when the user has logged in -- I've exposed the form for the login and you can see I start to check the this.props.authData states -- where it would be ideal to call persistor.rehydrate() -- but then I don't have access to the persistor or the store on those pages?
//router.js
import React, { Component } from 'react'
import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom'
import createBrowserHistory from 'history/createBrowserHistory'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
import {persistStore, autoRehydrate} from 'redux-persist'
import rootReducer from './reducers/rootReducer'
// components
import Login from './components/Login/Login'
import ForgotPassword from './components/ForgotPassword/ForgotPassword'
import RegisterUser from './components/RegisterUser/RegisterUser'
import Home from './components/Home/Home'
import Actions from './components/Actions/Actions'
import AddSDQ from './components/Actions/AddSDQ'
import PastSDQ from './components/PastSDQ/PastSDQ'
import Account from './components/Account/Account'
import Logout from './components/Logout/Logout'
import About from './components/About/About'
import Terms from './components/Terms/Terms'
import Privacy from './components/Privacy/Privacy'
import Error from './components/Error/Error'
import Header from './components/Header/Header'
import Footer from './components/Footer/Footer'
const history = createBrowserHistory()
// add `autoRehydrate` as an enhancer to your store (note: `autoRehydrate` is not a middleware)
const store = createStore(
rootReducer,
applyMiddleware(thunk),
autoRehydrate()
);
const persistor = persistStore(store, {}, () => {
console.log('restored');
})
// we can pass the lang files as props to the routes
// we should have a nested route inside service here to show the other services page
class Routes extends Component {
constructor (props) {
super(props)
/*
this.state = {
rehydrated: false
}*/
//console.log("router level", this.props)
//console.log("state-->", this.state)
}
componentWillMount(){
// begin periodically persisting the store
/*
persistStore(store, {}, () => {
this.setState({ rehydrated: true })
console.log("rehydrated", store);
})*/
}
render () {
console.log("this props", this);
// when the user has logged in - navigate them to the home page
if(this.props.authData){
//this.props.authData.isLogged
//return <Redirect to='/home'/>;
console.log("user logged!!!!!!!!!!!");
//persistor.rehydrate()//calls reducer to rehydrate store
}
const loggedIn = true;//this.state.isLoggedIn;
console.log("rendered store", store);
return (
<Provider store={store}>
<Router history={history}>
<div className='off-canvas-wrap' data-offcanvas>
<div className='inner-wrap'>
<Header transparent />
<Switch>
<Route path='/home' component={Home} />
<Route path='/past-sdq' component={PastSDQ} />
<Route path='/actions' component={Actions} />
<Route path='/add-sdq' component={AddSDQ} />
<Route path='/account' component={Account} />
<Route path='/about' component={About} />
<Route path='/terms' component={Terms} />
<Route path='/privacy' component={Privacy} />
<Route path='/login' component={Login} />
<Route path='/logout' component={Logout} />
<Route path='/forgot-password' component={ForgotPassword} />
<Route path='/register-user' component={RegisterUser} />
{/*<Route path='/api/:serviceRequest' />*/}
<Route exact path="/" render={() => ( loggedIn ? ( <Home/> ) : ( <Redirect to="/login"/> ) )} />
<Route component={Error} />
</Switch>
<Footer transparent />
</div>
</div>
</Router>
</Provider>
)
}
}
export default Routes
//rootReducer.js
import { combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form'
import { authReducer } from './authReducer'
import { regReducer } from './regReducer'
import { forgotReducer } from './forgotReducer'
import { homeReducer } from './homeReducer'
import { editProfileReducer } from './editProfileReducer'
import { initProfileReducer } from './initProfileReducer'
import { editFollowUpReducer } from './editFollowUpReducer'
import { initFollowUpReducer } from './initFollowUpReducer'
import { addSDQReducer } from './addSDQReducer'
import { pastSDQReducer } from './pastSDQReducer'
import { rehydrateReducer } from './rehydrateReducer'
const rootReducer = combineReducers({
form: formReducer,
auth: authReducer,
reg: regReducer,
forgot: forgotReducer,
home: homeReducer,
editProfile: editProfileReducer,
initProfile: initProfileReducer,
editFollowUp: editFollowUpReducer,
initFollowUp: initFollowUpReducer,
addSDQ: addSDQReducer,
pastSDQ: pastSDQReducer,
rehydrate: rehydrateReducer
})
export default rootReducer
//rehydrateReducer.js
import {REHYDRATE} from 'redux-persist/constants'
export function rehydrateReducer (state = {}, action) {
//console.log('reducer REHYDRATE act', action)
switch (action.type) {
case REHYDRATE:
return {...state, data: action.payload};
default:
return {...state}
}
}
//login.js
import React, { Component } from 'react'
import { withRouter, Redirect } from 'react-router-dom';
//import { withRouter, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { fetchAuthentication } from '../../actions/authAction';
import {persistStore} from 'redux-persist'
import { Row, Col } from 'antd';
// components
import LoginSyncValidationForm from './LoginSyncValidationForm'
import '../../forms.scss';
// this is a class because it needs state
class Login extends Component {
constructor(props, context) {
super(props, context);
this.submit = this.submit.bind(this);
}
/*
static propTypes = {
isDark: React.PropTypes.bool
}
static defaultProps = {
isDark: false
}
*/
componentDidMount() {
//document.body.classList.toggle('darkClass', this.props.isDark)
}
componentWillReceiveProps(nextProps) {
//document.body.classList.toggle('darkClass', nextProps.isDark)
}
componentWillMount () {
document.body.classList.add('screenbackground');
}
componentWillUnmount() {
document.body.classList.remove('screenbackground');
}
submit(data) {
this.props.fetchAuthentication(data);
}
render() {
var errorPlaceholder = "";
//console.log("authss-->", this.props.authData);
if(this.props.authData.data){
//console.log("status--<", this.props.authData.data.data.status);
//if error from server side show the message
if(this.props.authData.data.data.status !== "success"){
errorPlaceholder = this.props.authData.data.data.msg;
}
}
// when the user has logged in - navigate them to the home page
if(this.props.authData.isLogged){
//return <Redirect to='/home'/>;
//persistor.rehydrate()//calls reducer to rehydrate store
}
return (
<div className="Page form-components light">
<h2>Login</h2>
<Row>
<Col xs={24} sm={24} md={10}>
<p>Welcome to the SLAM SDQ tracker. Because you are accessing sensitive info, you need to verify your identity using our secure login system. This will not only protect your data, but will provide a platform where you can be in control of your progress. Your unique identification number has been sent to you by e-mail. Use it to login. If you have not created an account yet or have forgotten your password, please use the links below to complete the desired action.</p>
</Col>
<Col xs={24} sm={24} md={24}>
<Row>
<Col xs={24} sm={24} md={6}>
<LoginSyncValidationForm onSubmit={this.submit} />
</Col>
</Row>
</Col>
{errorPlaceholder.length > 0 &&
<Col xs={24} sm={24} md={24}>
{errorPlaceholder}
</Col>
}
</Row>
<div className="shell" />
<div className="screen-background login"/>
</div>
)
}
}
function mapStateToProps(state) {
return {
authData: state.auth
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchAuthentication }, dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Login))
Update 1: 14/09/2017
this is what my authAction.js looks like
//authAction.js
import axios from 'axios';
export const FETCH_AUTH_SUCCESS = 'FETCH_AUTH_SUCCESS'
export const FETCH_AUTH_FAILURE = 'FETCH_AUTH_FAILURE'
export const FETCH_AUTH_CLEAR = 'FETCH_AUTH_CLEAR'
export function authSuccess(response) {
return {
type: FETCH_AUTH_SUCCESS,
payload: response
}
}
export function authFail(response) {
return {
type: FETCH_AUTH_FAILURE,
payload: response
}
}
export function authClear() {
return {
type: FETCH_AUTH_CLEAR,
payload: null
}
}
export function fetchAuthentication(data) {
let url = 'http://localhost:8080/login';
return function (dispatch) {
//axios.post(url, data)
axios.get(url, {
params: data
})
.then(function (response) {
console.log(response);
if(response.data.status === "success"){
dispatch(authSuccess(response));
}
else{
// fail - user not found for example
dispatch(authFail(response));
}
})
.catch(function (error) {
//console.log(error);
dispatch(authFail(error));
});
}
}
export function clearAuthentication() {
let url = 'http://localhost:8080/logout';
return function (dispatch) {
//axios.post(url, data)
axios.get(url)
.then(function (response) {
console.log(response);
if(response.data.status === "success"){
dispatch(authClear(response));
}
else{
// fail - user not found for example
dispatch(authFail(response));
}
})
.catch(function (error) {
//console.log(error);
dispatch(authFail(error));
});
}
}
//authReducer.js
import { FETCH_AUTH_SUCCESS, FETCH_AUTH_FAILURE, FETCH_AUTH_CLEAR } from '../actions/authAction'
export function authReducer (state = {}, action) {
//console.log('reducer act', action)
switch (action.type) {
case FETCH_AUTH_SUCCESS:
return {...state, data: action.payload, isLogged: true};
case FETCH_AUTH_FAILURE:
return {...state, data: action.payload, isLogged: false};
case FETCH_AUTH_CLEAR:
return {...state, data: action.payload, isLogged: false};
default:
return {...state}
}
}
as per "Steven Daniel Anderson" answer
"
const user = localStorage.getItem('user')
if(user){
//Set the state authenticated to true;
store.dispatch({
type:AUTH_USER
})
}
"
be something more like
const user = localStorage.getItem('user')
if(user){
//Set the state authenticated to true;
authSuccess(user)
}
or in my action create a new function like this
export function fetchResumeAuth(data) {
dispatch(authSuccess(data));
}
and then in the user check -- which would be placed in the router.js?
const user = localStorage.getItem('user')
if(user){
//Set the state authenticated to true;
fetchResumeAuth(user)
}