8

I'm looking to re render/refresh, after a user has logged in, so im using history.push to do that.

import {history} from '../layout/Navbar'
export const loginUser = userData => dispatch => {
    Axios.post('/users/login', userData)
        .then( res => {
            // retrieve token from the response 
            const token = res.data.token;
            // console.log(token);
            // pass the token in session
            sessionStorage.setItem("jwtToken", token);
            // set the auth token
            setAuthToken(token);

            // decode the auth token
            const decoded = jwt_decode(token);
            // pass the decoded token
            dispatch(setCurrentUser(decoded))
            history.push('/dashboard');

        })
        .catch(err => {
            if(err.response.data){
                console.log(err.response)
                dispatch({
                    type: GET_ERRORS,
                    payload: err.response.data
                })
            }
        })
}

export const getUser = () => {
    return (dispatch) => {
        return Axios.get('/users/current_user',{
        }).then( res => {
            const data = res.data
            dispatch({type: GET_CURRENT_USER, data})
        })
    }
}

export const setCurrentUser = (decoded, dispatch) => {
    return{
        type:SET_CURRENT_USER,
        payload:decoded,

    }

}

Instead it doesn't seem to re-render because im getting an error that only should have an error if a user isn't logged in.

For example

TypeError: Cannot read property 'user' of undefined

on one of my components.

On refresh on the dashboard page, the error goes away, so im looking for a way to re render the state, and redirect to dashboard that way a user will exist or throw an unauthorized error if given the wrong credentials.

Navbar.js

import React, {Component} from "react";
import {BrowserRouter, Link, Route, Switch} from "react-router-dom";
import PrivateRoute from '../components/PrivateRoute';
import Home from '../components/Home';
import Dashboard from '../components/Dashboard';
import {connect} from 'react-redux';
import Login from '../components/Login';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import {logoutUser} from '../actions/authActions';
import SignUp from "../components/SignUp";
import Grid from '@material-ui/core/Grid';
import {createBrowserHistory} from 'history';

// import createBrowserHistory from 'history/createBrowserHistory'

export const history = createBrowserHistory()

class Navbar extends Component {
    logout = (e) => {
        e.preventDefault();
        this.props.logoutUser();

    }
    render() {
        // LINKS
        const authLinks = (
            <span>
                <Button>
                    <Link to="/dashboard" color="primary">
                        Dashboard
                    </Link>
                </Button>       
                <Button onClick={this.logout} to="/">
                    <span>Logout</span>
                </Button>
            </span>

        );
        const guestLinks = (
            <span>
                <Button>
                    <Link to="/login">
                        Login
                    </Link>
                </Button>

                <Button>
                    <Link to="/signup">
                       Sign Up
                    </Link>

                </Button>
            </span>
        );
        return (
            <div>
                <BrowserRouter history={history}>

                    <AppBar position="static">
                        <Toolbar>
                        <Grid justify="space-between" container >
                            <Typography variant="h6" style={{ color: '#fff'}}>
                               Image Upload App
                            </Typography>                         

                             {/* if is authenticated, will render authlinks 
                                if not will render guest links
                            */}
                            <Grid item>
                                <Button align="right">
                                    <Link style={{ color:'#fff'}} underline="none" to="/">
                                        Home
                                    </Link>
                                </Button>
                                 {this.props.auth.isAuthenticated ? authLinks : guestLinks}

                            </Grid>




                        </Grid>
                        </Toolbar>
                    </AppBar>
                    <Switch>
                        <Route exact path="/" component={Home}/>
                        <Route exact path="/signUp" component ={SignUp}/>
                        <Route exact path="/login" component={Login}/> {/* private routes for users who are authenticated */}
                        <PrivateRoute exact path="/dashboard" component={Dashboard}></PrivateRoute>

                    </Switch>
                </BrowserRouter>
            </div>
        )
    }
}

const mapDispatchToProps = (dispatch) => ({
    logoutUser: () => dispatch(logoutUser())
})
const mapStateToProps = (state) => ({
    auth: state.auth
})
export default connect(mapStateToProps,mapDispatchToProps)(Navbar)

App.js

import React, { Component } from 'react';
import './App.css';
import setAuthToken from "./actions/utils/setAuthToken";
import Navbar from './layout/Navbar';
import jwt_decode from "jwt-decode";
import store from './store';
import {setCurrentUser, logoutUser, getUser } from './actions/authActions';
import { Provider } from "react-redux";

// JWT TOKEN
if (sessionStorage.jwtToken) {
  // Set auth token header auth
  setAuthToken(sessionStorage.jwtToken);
  // Decode token and get user info and exp
  const decoded = jwt_decode(sessionStorage.jwtToken);
  // Set user and isAuthenticated
  store.dispatch(setCurrentUser(decoded));

  store.dispatch( getUser());

  // Check for expired token
  const currentTime = Date.now() / 1000;
  if (decoded.exp < currentTime) {

    // Logout user
    store.dispatch(logoutUser());
    // Redirect to login
    window.location.href = "/login";
  }



}
class App extends Component {
  render(){
    return (
      <Provider store={store}>
         <Navbar/>
      </Provider>
    );
  }
}
export default App;

Login

import React, { Component } from "react";
import {connect} from 'react-redux';
import {Redirect} from "react-router-dom";
import {loginUser, googleLogin } from '../actions/authActions';
import Grid from '@material-ui/core/Grid';
import PropTypes from "prop-types";
import { GoogleLogin } from "react-google-login";
import Divider from '@material-ui/core/Divider';
import Typography from '@material-ui/core/Typography';
import { GoogleLoginButton} from "react-social-login-buttons";
import LoginForm from './LoginForm/LoginForm';
import {history} from '../layout/Navbar';
import { withRouter } from "react-router-dom";
// const onSuccess = response => console.log(response);
// const onFailure = response => console.error(response);
class Login extends Component{
    constructor() {
        super();
        this.state = {
            formData:{
                username:'',
                password:'',
                isAuthenticated: false,
            },
            errors:{}
        }
    }
    logInGithub = (e) => {
        e.preventDefault();
        console.log('hello');
        this.props.githubLogin();
    }
    componentDidMount() {
        // console.log(this.props.auth);
        if (this.props.auth.isAuthenticated) {
          this.props.history.push("/dashboard");
        }
      }
    componentDidUpdate(){
        if(this.props.auth.isAuthenticated){
            this.props.history.push("/dashboard")
        }
    }

    handleChange = (e) => {
        e.preventDefault();
        const {formData} = this.state;
        this.setState({
            formData: {
                ...formData,
                [e.target.name]: e.target.value
            }
        });
    }
    handleSubmit = (e) => {
        e.preventDefault();
        const {formData} = this.state;
        const {username, password} = formData;
        const creds = {
            username,
            password
        }
        this.props.loginUser(creds,  this.props.history);


        // console.log(creds);
    }
    render(){
        const googleLogin = response => {
            let googleData;
            googleData = {
              googleID: response.profileObj.googleId,
              email: response.profileObj.email,
            //   password: "",
            };
            console.log(googleData);
            this.props.googleLogin(googleData);
          };
        return(
            <div>
            <Grid container justify="center" spacing={0}>
                <Grid item  sm={10} md={6} lg={4} style={{ margin:'20px 0px'}}>
                <Typography variant="h4" style={{ letterSpacing: '2px'}} >
                     Sign In
                </Typography>
                {this.props.auth.errors ? (
                    this.props.auth.errors.map( (err, i) => (
                        <div key={i} style={{color: 'red' }}>
                            {err} 
                        </div>
                    ))                  
                ):(
                    null
                )}
                <LoginForm 
                    mySubmit={this.handleSubmit}
                    myChange={this.handleChange}
                    username={this.state.username}
                    password={this.state.password}
                />
                    <Grid item sm={12}>
                            <Typography align="center" variant="h4" style={{ letterSpacing: '6px'}} >
                                OR
                            </Typography>
                        <Divider style={{ width: '200px', margin:'20px auto', backgroundColor:'#000000'}} variant="middle" />
                    </Grid>

                </Grid>
            </Grid>
            </div>
        )
    }
}
Login.propTypes = {
    loginUser: PropTypes.func.isRequired,
    auth: PropTypes.object.isRequired,
    errors: PropTypes.object
};
const mapStateToProps = (state) => ({
    auth: state.auth
})
const mapDispatchToProps = (dispatch) => ({
    loginUser: (userData) => dispatch(loginUser(userData)),
    googleLogin: (userData) => dispatch(googleLogin(userData))
})
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Login))
5
  • um why are you using history.push why not just use <Redirect /> from react router? Commented Jun 25, 2019 at 5:43
  • Does that work in a redux action ? Commented Jun 25, 2019 at 5:45
  • you dont have to write that in the redux action.... you can call the redux action and then call <Redirect/> depending on the login success or failure Commented Jun 25, 2019 at 5:48
  • Error is something else and not with history.push. post a complete error so that we can see where is it failing. Commented Jun 25, 2019 at 5:52
  • @BARNOWL Try wrapping the component that needs to be re-rendered in withRouter() from react-router: export default connect(mapStateToProps,mapDispatchToProps)(withRouter(Navbar)) Commented Jun 25, 2019 at 6:13

5 Answers 5

9

So it appears that i wasn't using the navbar history,

so instead of this.props.history.push("/dashboard")

I used history.push('/dashboard')

Login.js

  .....
  componentDidMount() {
        // console.log(this.props.auth);
        if (this.props.auth.isAuthenticated) {
          history.push("/dashboard");
        }
      }
   componentDidUpdate(){
        if(this.props.auth.isAuthenticated){
            history.push("/dashboard")
        }
    }

Navbar.js

and change this

export const history = createBrowserHistory()

to this

export const history = createBrowserHistory({forceRefresh:true})

This is without using withRouter() instances.

I still had to remove history.push from the authActions.js loginUser function.

Sign up to request clarification or add additional context in comments.

1 Comment

@Christopher if their is a better approach or cleaner approach let me know.
2

I think what's happening is the push to your Dashboard component is executing before the setCurrentUser() action has time to complete.

dispatch(setCurrentUser(decoded))
history.push('/dashboard');

These two lines of code are running to execution, not synchronously (one after the other) as it may appear.

TypeError: Cannot read property 'user' of undefined

This likely comes from your Dashboard component. You're probably using something like this.props.auth.user in some capacity, where this.props.auth is not yet valid.

We can configure your code to have a more synchronous flow of actions.

You're probably calling the loginUser() action-creator inside your Login component (makes sense). Let's make sure we bring in withRouter from react-router-dom and the auth-state from your reducer at the minimum. By bringing in those two, we can have the redirect handled in componentDidUpdate()

So at the minimum the Login component will use the following imports and life-cycle methods like so:

import React from "react"
import { connect } from "react-redux"
import { loginUser } from "../../actions/authActions"
import { withRouter } from "react-router-dom"

class Login extends React.Component{
    componentDidMount(){
        if(this.props.auth.isAuthenticated){
            this.props.history.push("/dashboard")
        }
    }

    componentDidUpdate(){
        if(this.props.auth.isAuthenticated){
            this.props.history.push("/dashboard")
        }
    }

    onSubmit = (event) => {
        event.preventDefault()

        const userData = {
            email: this.state.email,
            password: this.state.password
        }

        this.props.loginUser(userData)
     }

    render(){
        return(
           ...yourmarkup
        )
    }
}

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


const mapDispatchToProps = (dispatch) => {
    return{
        loginUser: (userData) => {
            dispatch(loginUser(userData))
        }
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Login))

Note, in your authActions file, we can remove the history.push("/dashboard") line, since we are calling the redirect from the Login component.

The synchronous flow would be:

  1. You submit the login form inside Login component, calling your loginUser() action-creator.
  2. loginUser executes, it dispatches setCurrentUser and we pass in the decoded user.
  3. Your reducer returns a new state, now using the authenticated user.
  4. Login component receives new props from the redux-store, causing the component to re-render.
  5. componentDidUpdate() is triggered, we verify that the auth-state has an authenticated user. If true, we redirect to the Dashboard component.

2 Comments

will give this a shot, and let you know how it went thanks chris.
thanks for the effort, but this still give the same error. I updated code to show Login component.
1

You can use withRouter hoc and then use props.history -

import { withRouter } from 'react-router'

const Navbar = props => {
   return (
     <div onClick={() => props.history.push('your-path')}>
        Navigate to your-path
     </div>
   )
}

export default withRouter(Navbar)

1 Comment

The withRouter component must be within a <Router> or Router-derived component such as <MemoryRouter>.
0

Try this if you are using 'React > 16.8.0' and functional components.

import { useHistory } from "react-router-dom";

Use in your functional component:

const history = useHistory();
history.push('/dashboard');

Comments

0

There is currently a compatibility issue between the latest ReactRouter (5.2) and the latest history (5.0).

One of the symptoms of this incompatibility is history.push() calls not triggering a re-render. Downgrading history to v4 should fix the problem.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.