1

I am using react with redux to build login authentication system with backend passport jwt .

login works fine before , i have added PrivateRoute to some Routes that requires authentication.

Errors i got :

enter image description here enter image description here

src/actions/authActions.js

import { GET_ERRORS,CLEAR_ERRORS,SET_CURRENT_USER,LOGOUT_USER} from './types';
import axios from 'axios';
import setAuthToken from '../utils/setAuthToken';
import jwt_decode from 'jwt-decode';

export const loginUser= userdata =>dispatch=>{
    axios.post('/api/auth/login',userdata)
         .then(res=>{
             console.log('loginUser action response ==>',res.data);
             const {token}=res.data;
             localStorage.setItem('jwtToken',token);
             setAuthToken(token); 
             // Decode token to get user data
             const decoded = jwt_decode(token);
             dispatch(setCurrentUser(decoded));
         }).catch(err=>{
             dispatch({type:GET_ERRORS,payload:err.response.data});
         })
}

// Set logged in user
export const setCurrentUser = decoded => {
    return {
      type: SET_CURRENT_USER,
      payload: decoded
    };
  };

src/reducers/authReducers.js

import isEmpty from '../validation/is-empty';
import { SET_CURRENT_USER,LOGIN_USER,LOGOUT_USER} from '../actions/types';

const initialState = {
    isAuthenticated: false,
    user: {}
  };

  export default function(state = initialState, action) {
    switch (action.type) {

        case LOGIN_USER:
        case SET_CURRENT_USER:
            return {
                ...state,
                isAuthenticated: !isEmpty(action.payload),
                user: action.payload
            };

        case LOGOUT_USER:
        return {
            ...state,
            isAuthenticated:false,
            user: {}
        };

        default:
        return state;
    }
}

App.js

import React, { Component } from 'react';
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
import {Provider} from 'react-redux';
import store from './store';
import Footer from './partials/footer';
import Header from './partials/header';

import Login from './components/auth/login';
import { setCurrentUser ,logoutUser} from './actions/authActions';
import  jwt_decode  from 'jwt-decode';
import setAuthToken from './utils/setAuthToken';
import PrivateRoute from './utils/PrivateRoute';
import Dashboard from './components/user/dashboard';
import NotFound404 from './components/error/404';

if(localStorage.jwtToken){
  setAuthToken(localStorage.jwtToken);
  // Decode token and get user info and exp
  const decoded = jwt_decode(localStorage.jwtToken);
  store.dispatch(setCurrentUser(decoded));
    // Check for expired token
    const currentTime = Date.now() / 1000;
    if (decoded.exp < currentTime) {
      // Logout user
      store.dispatch(logoutUser());
      // Clear current Profile
      //store.dispatch(clearCurrentProfile());
      // Redirect to login
      window.location.href = '/login';
    }
}

export default class App extends Component {
  constructor(){
    super();
    this.state={
      isAuthenticated:store.getState().auth.isAuthenticated
    }
  }

  render() {
    return ( 
      <Provider store={store}>
      <Router>

      <div className="App">
        <Header/>
        <div className="container">
        <Switch>
        <Route exact path="/" component={Home}/>
        <Route exact path="/login" component={Login} />

        <PrivateRoute isAuthenticated={this.state.isAuthenticated} exact path="/dashboard" component={Dashboard}/>

        <Route component={NotFound404} />
        </Switch>
        </div>
        <Footer/>
      </div>

      </Router>
      </Provider>
    );
  }
}

src/components/login.js

import React, { Component } from 'react'
import { Link } from 'react-router-dom';
import classnames from 'classnames';
import { connect } from 'react-redux';
import { loginUser } from '../../actions/authActions';
import { PropTypes } from 'prop-types';

class Login extends Component {
    constructor(){
        super();
        this.state={
            email:'',
            password:'',
            errors:{}
        }
        this.handleChange=this.handleChange.bind(this);
        this.handleSubmit=this.handleSubmit.bind(this);
    }

    handleChange(event){
        this.setState({
            [event.target.name]:event.target.value
        });
    }

    handleSubmit(event){
        event.preventDefault();
        const user={
            email:this.state.email,
            password:this.state.password
        }
        this.props.loginUser(user);
    }


    componentDidMount() {
        if (this.props.auth.isAuthenticated) {
        this.props.history.push('/dashboard');
        }
    }

    componentWillReceiveProps(nextProps){

        if(nextProps.errors){
            this.setState({
                errors:nextProps.errors 
            });
        }

        if(nextProps.auth.isAuthenticated){
            this.props.history.push('/dashboard');
        }
    }

    render () {
        const {errors} = this.state;
        return (
            <div className="row my-5">
            <div className="col-md-4 offset-md-4 col-sm-12">
            <div className="card shadow-sm">
            <h5 className="card-header">Login</h5>
            <div className="card-body">
                <form onSubmit={this.handleSubmit}>
                    <div className="form-group">
                    <label htmlFor="email" className="label">Email</label>
                    <input type="email" id="email" name="email"  value={this.state.email} onChange={this.handleChange} className={classnames('form-control',{'is-invalid':errors.email})}/>
                    {errors.email && (<div className="invalid-feedback">{errors.email}</div>)}

                    </div>

                    <div className="form-group">
                    <label htmlFor="password" className="label">Password</label>
                    <input type="password" id="password" name="password"  value={this.state.password} onChange={this.handleChange}  className={classnames('form-control',{'is-invalid':errors.password})}/>
                    {errors.password && (<div className="invalid-feedback">{errors.password}</div>)}

                    </div>

                    <button type="submit" className="btn btn-success btn-block">Login</button>

                </form>
                <div className="py-3 border-bottom"></div>
                <Link to="/register" className="btn btn-default btn-block my-2">Haven't created account yet ?</Link>
                <Link to="/forgotpassword" className="btn btn-default btn-block">Forgot Password ?</Link>
            </div>
          </div>
            </div>
            </div>

        )
    }
}

const mapStateToProps = (state, ownProps) => ({
    auth:state.auth,
    errors:state.errors
})

const mapDispatchToProps = {
    loginUser
}

Login.propTypes={
    auth:PropTypes.object.isRequired,
    errors:PropTypes.object.isRequired, 
    loginUser:PropTypes.func.isRequired
}


export default connect(mapStateToProps,mapDispatchToProps)(Login)

PrivateRoute.js component

import React from 'react';
import {Route,Redirect} from 'react-router-dom';

const PrivateRoute=({component: Component, isAuthenticated, ...rest}) => {
    return (
        <Route
          {...rest}
          render={(props) => isAuthenticated === true
            ? <Component {...props} />
            : <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
        />
      )
}
export default PrivateRoute;

Please help me to solve this error .

Saurabh Mistry
  • 8,903
  • 4
  • 33
  • 51

2 Answers2

1

I have solved my error by replacing PrivateRoute component as below :

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

const PrivateRoute = ({ component: Component,auth, ...rest }) => (
  <Route
    {...rest}
    render={props =>
      auth.isAuthenticated === true ? (
        <Component {...props} />
      ) : (
        <Redirect to="/login" />
      )
    }
  />
);

PrivateRoute.propTypes = {
  auth: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  auth: state.auth
});

export default connect(mapStateToProps)(PrivateRoute);
Saurabh Mistry
  • 8,903
  • 4
  • 33
  • 51
0

I suggest you use another state variable for saving request status. like loggingIn if it's true, show loading if it's false and isAuthenticated is false, the user is not requested for login and it's not logged in. so redirect it to /login.

PrivateRoute.js component

import React from 'react';
import {Route,Redirect} from 'react-router-dom';

class PrivateRoute extends Component {
render() {
    const {
        component: Component, loggingIn, isAuthenticated, ...rest
    } = this.props;
    if (loggingIn) {
        return (
            <div>
                Please wait.
            </div>
        );
    }
    return (<Route {...rest} render={props => (isAuthenticated ? (<Component {...props} />) : (<Redirect to={{ pathname: '/login', state: { from: props.location } }} />))} />);
}}
export default PrivateRoute;

src/actions/authActions.js

import { GET_ERRORS,CLEAR_ERRORS,SET_CURRENT_USER,LOGOUT_USER} from './types';
import axios from 'axios';
import setAuthToken from '../utils/setAuthToken';
import jwt_decode from 'jwt-decode';

export const loginUser= userdata =>dispatch=>{
    dispatch(loggingIn(true));
    axios.post('/api/auth/login',userdata)
         .then(res=>{
             dispatch(loggingIn(false));
             console.log('loginUser action response ==>',res.data);
             const {token}=res.data;
             localStorage.setItem('jwtToken',token);
             setAuthToken(token); 
             // Decode token to get user data
             const decoded = jwt_decode(token);
             dispatch(setCurrentUser(decoded));
         }).catch(err=>{
             dispatch(loggingIn(false));
             dispatch({type:GET_ERRORS,payload:err.response.data});
         })
}

// Set logged in user
export const setCurrentUser = decoded => {
    return {
      type: SET_CURRENT_USER,
      payload: decoded
    };
  };

export const loggingIn = status => {
    return {
        type: 'LOGGINGIN',
        status,
    }
}

src/reducers/authReducers.js

import isEmpty from '../validation/is-empty';
import { SET_CURRENT_USER,LOGIN_USER,LOGOUT_USER} from '../actions/types';

const initialState = {
    isAuthenticated: false,
    user: {}
  };

  export default function(state = initialState, action) {
    switch (action.type) {

        case LOGIN_USER:
        case SET_CURRENT_USER:
            return {
                ...state,
                isAuthenticated: !isEmpty(action.payload),
                user: action.payload
            };

        case LOGOUT_USER:
        return {
            ...state,
            isAuthenticated:false,
            user: {}
        };
        case 'LOGGINGIN':
            return {
                ...state,
                loggingIn: action.status,
            };
        default:
        return state;
    }
}

and remember pass loggingIn as props to privateRoute

Edit : show how to use in App.js

App.js

import React, { Component } from 'react';
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
import {connect} from 'react-redux';
import Footer from './partials/footer';
import Header from './partials/header';

import Login from './components/auth/login';
import { setCurrentUser ,logoutUser} from './actions/authActions';
import  jwt_decode  from 'jwt-decode';
import setAuthToken from './utils/setAuthToken';
import PrivateRoute from './utils/PrivateRoute';
import Dashboard from './components/user/dashboard';
import NotFound404 from './components/error/404';


class App extends Component {
  constructor(props){
    super(props);
    const { dispatch } = props;
    if(localStorage.jwtToken){
        setAuthToken(localStorage.jwtToken);
        // Decode token and get user info and exp
        const decoded = jwt_decode(localStorage.jwtToken);
        dispatch(setCurrentUser(decoded));
        // Check for expired token
        const currentTime = Date.now() / 1000;
        if (decoded.exp < currentTime) {
           // Logout user
          dispatch(logoutUser());
          // Clear current Profile
          //dispatch(clearCurrentProfile());
          // Redirect to login
          window.location.href = '/login';
       }
    }
  }

  render() {
    const { isAuthenticated, loggingIn } = this.props;
    return ( 
      <Provider store={store}>
      <Router>

      <div className="App">
        <Header/>
        <div className="container">
        <Switch>
        <Route exact path="/" component={Home}/>
        <Route exact path="/login" component={Login} />

        <PrivateRoute loggingIn={loggingIn} isAuthenticated={isAuthenticated} exact path="/dashboard" component={Dashboard}/>

        <Route component={NotFound404} />
        </Switch>
        </div>
        <Footer/>
      </div>

      </Router>
      </Provider>
    );
  }
}
const mapStateToProps = state = {
   const { loggingIn, isAuthenticated } = state.auth;
   return { loggingIn, isAuthenticated }
}
export default connect(mapStateToProps)(App);
Mohammad Rajabloo
  • 1,369
  • 12
  • 16