简体   繁体   中英

React app requires hard refresh after successful login

I'm struggling to debug a problem and I'd appreciate any help the community might be able to offer. I'm building my first React app and have built a working Login feature, but after every successful login the user is forced to hard refresh his/her browser in order see the app in a "logged in" state. There is no error logged to the browser console, but our DevTools monitor shows the following error:

"TypeError: Cannot read property 'setState' of undefined"

What's funny is that the login authentication first succeeds, and then immediately seems to try again and fails. After clicking "login," the user must hard refresh the web page in order to make it appear that the login has worked.

I'm stumped. Can anyone see anything wrong with my code? Thank you very much in advance for taking a look!

Here's our LoginPage jsx file that contains the actual login web form:

import React from 'react';
import { Button, Form, FormGroup, Label, Input } from 'reactstrap';

export default class LoginPage extends React.Component {
    constructor(props) {
        super(props);

        //bound functions
        this.compileFormData = this.compileFormData.bind(this);
        this.handleEmailChange = this.handleEmailChange.bind(this);
        this.handlePasswordChange = this.handlePasswordChange.bind(this);

        //component state
        this.state = {
            email: '',
            password: '',
        };
    }

    //update state as email value changes
    handleEmailChange(e) {
        this.setState({ email: e.target.value });
    }

    //update state as password value changes
    handlePasswordChange(e) {
        this.setState({ password: e.target.value });
    }

    compileFormData() {
        const { loginFunction } = this.props;
        const formData = this.state;
        loginFunction(formData);
    }

    render() {
        return (
            <div className="row justify-content-center">
                <div className="col-10 col-sm-7 col-md-5 col-lg-4">
                    <Form>
                        <FormGroup>
                            <Label for="exampleEmail">Email</Label>
                            <Input
                                type="email"
                                name="email"
                                id="userEmail"
                                placeholder="test@mccre.com"
                                value={this.state.email}
                                onChange={this.handleEmailChange}
                            />
                        </FormGroup>
                        <FormGroup>
                            <Label for="examplePassword">Password</Label>
                            <Input
                                type="password"
                                name="password"
                                id="userPassword"
                                placeholder="password"
                                value={this.state.password}
                                onChange={this.handlePasswordChange}
                            />
                        </FormGroup>
                        <Button onClick={this.compileFormData}>Log In</Button>
                    </Form>
                </div>
            </div>
        );
    }
 }

Here's our login page Container that renders the login page:

import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { logUserIn } from '../../actions/authentication';

import LoginPage from './LoginPage';

export class LoginPageContainer extends React.Component {
    constructor(props) {
        super(props);

        //bound functions
        this.logUserInFunction = this.logUserInFunction.bind(this);

    }

    logUserInFunction(userData) {
        const { dispatch } = this.props;
        dispatch(logUserIn(userData));
    }

    render() {

        const { authentication } = this.props;

        if (authentication.isLoggedIn) {
            return (
                <Redirect to="/" />
            );
        }

        return (
            <div>
                <LoginPage loginFunction={this.logUserInFunction} />
            </div>
        );
    }

}

function mapStateToProps(state) {
    return {
        authentication: state.authentication,
    };
}

export default connect(mapStateToProps)(LoginPageContainer);

Here's our API endpoint in which we actually make the database query:

const express = require('express');
const mongoose = require('mongoose');
const passport = require('passport');
const User = require('../../models/user.js');

const router = express.Router();
//configure mongoose promises
mongoose.Promise = global.Promise;

//POST to /register
router.post('/register', (req, res) => {
    //Create a user object to save, using values from incoming JSON
    const newUser = new User({
        username: req.body.username,
        firstName: req.body.firstName,
        lastname: req.body.lastName,
        email: req.body.email,
    });

    //Save, via passport's "register" method, the user
    User.register(newUser, req.body.password, (err, user) => {
        //If there's a problem, send back a JSON object with the error
        if (err) {
            return res.send(JSON.stringify({ error: err }));
        }
        // Otherwise, for now, send back a JSON object with the new user's info
        return res.send(JSON.stringify(user));
    });
});

//POST to /login
router.post('/login', async (req, res) => {
    //look up user by their email
    const query = User.findOne({ email: req.body.email });
    const foundUser = await query.exec();

    //If they exist, they'll have a username, so add that to our body
    if (foundUser) { 
        req.body.username = foundUser.username; 
    }

    passport.authenticate('local') (req, res, () => {
        //If logged in, we should have use info to send back
        if (req.user) {
            return res.send(JSON.stringify(req.user));
        }

        //Otherwise return an error
        return res.send(JSON.stringify({ error: 'There was an error logging in' }));
    });
});

//GET to /checksession
router.get('/checksession', (req, res) => {
    if (req.user) {
        return res.send(JSON.stringify(req.user));
    }
    return res.send(JSON.stringify({}));
});

//GET to /logout
router.get('/logout', (req, res) => {
    req.logout();
    return res.send(JSON.stringify(req.user));

});

module.exports = router;

Here's the action file in which we define the logUserIn() function:

import { decrementProgress, incrementProgress } from './progress';
import 'whatwg-fetch';

//Action Creators
export const loginAttempt = () => ({ type: 'AUTHENTICATION_LOGIN_ATTEMPT' });
export const loginFailure = error => ({ type: 'AUTHENTICATION_LOGIN_FAILURE', error });
export const loginSuccess = json => ({ type: 'AUTHENTICATION_LOGIN_SUCCESS', json });
export const logoutFailure = error => ({ type: 'AUTHENTICATION_LOGOUT_FAILURE', error });
export const logoutSuccess = () => ({ type: 'AUTHENTICATION_LOGOUT_SUCCESS' });
export const sessionCheckFailure = () => ({ type: 'AUTHENTICATION_SESSION_CHECK_FAILURE'});
export const sessionCheckSuccess = json => ({ type: 'AUTHENTICATION_SESSION_CHECK_SUCCESS', json });

//Check User Session
export function checkSession() {
    return async (dispatch) => {

        //contact the API
        await fetch(
            //where to contact
            '/api/authentication/checksession',
            //what to send
            {
                method: 'GET',
                credentials: 'same-origin',
            },
        )
        .then((response) => {
            if (response.status === 200) {
                return response.json();
            }
            return null;
        })
        .then((json) => {
            if (json.username) {
                return dispatch(sessionCheckSuccess(json));
            }
            return dispatch(sessionCheckFailure());
        })
        .catch((error) => dispatch(sessionCheckFailure(error)));
    };
}

//Log user in
export function logUserIn(userData) {
    return async (dispatch) => {
        
        //turn on spinner 
        dispatch(incrementProgress());

        //register that a login attempt is being made
        dispatch(loginAttempt());

        //contact login API
        await fetch(
            //where to contact
            'http://localhost:3000/api/authentication/login',
            //what to send
            {
                method: 'POST',
                body: JSON.stringify(userData),
                headers: {
                    'Content-Type': 'application/json',
                },
                credentials: 'include',
            },
        ).then((response) => {
            if (response.status === 200) {
                return response.json();
            }
            return null;
        })
        .then((json) => {
            if (json) {
                dispatch(loginSuccess(json));
                this.setState({ redirect: true });
            } else {
                dispatch(loginFailure(new Error('Authentication Failed')));
            }
        }).catch((error) => {
            dispatch(loginFailure(new Error(error)));
        });

        //turn off spinner
        dispatch(decrementProgress());
    };
}


//Log user out
export function logUserOut() {
    return async (dispatch) => {    

        //turn on spinner
        dispatch(incrementProgress());

        //contact the API
        await fetch(
            //where to contact
            '/api/authentication/logout',
            //what to send
            {
                method: 'GET',
                credentials: 'same-origin',
            },
        )
        .then((response) => {
            if (response.status === 200) {
                dispatch(logoutSuccess());
            } else {
                dispatch(logoutFailure(`Error: ${response.status}`));
            }
        })
        .catch((error) => {
            dispatch(logoutFailure(error));
        });

        //turn off spinner
        return dispatch(decrementProgress());;
    };
}

Finally, here's the reducer file that is supposed to update the application's state depending on authentication success / failure:

const initialState = {
    firstName: '',
    id: '',
    isLoggedIn: false,
    isLoggingIn: false,
    lastName: '',
    username: '',
};

export default function reducer(state = initialState, action) {
    switch (action.type) {
        case 'AUTHENTICATION_LOGIN_ATTEMPT': {
            const newState = Object.assign({}, state);
            newState.isLoggingIn = true;
            return newState;
        }
        case 'AUTHENTICATION_LOGIN_FAILURE': 
        case 'AUTHENTICATION_SESSION_CHECK_FAILURE': 
        case 'AUTHENTICATION_LOGOUT_SUCCESS': {
            const newState = Object.assign({}, initialState);
            return newState;
        }
        case 'AUTHENTICATION_LOGIN_SUCCESS': 
        case 'AUTHENTICATION_SESSION_CHECK_SUCCESS': {
            const newState = Object.assign({}, state);
            newState.firstName = action.json.firstName;
            newState.id = action.json._id;
            newState.isLoggedIn = true;
            newState.isLoggingIn = false;
            newState.lastName = action.json.lastName;
            newState.username = action.json.username;
            return newState;
        }
        case 'AUTHENTICATION_LOGOUT_FAILURE': {
            //todo: hanle error
            return state;
        }
        default: {
            return state;
        }
    }
}

Found the solution: "the this.setState({ redirect: true });" line needed to be removed from the action file.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM