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))
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.
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:
Login
component, calling your
loginUser()
action-creator.loginUser
executes, it dispatches setCurrentUser
and we pass in the
decoded user.Login
component receives new props from the redux-store, causing
the component to re-render.componentDidUpdate()
is triggered, we verify that the
auth-state has an authenticated user. If true, we redirect to the Dashboard
component.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)
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