0

I am developing a (simple-to-use) note-taking web application on my localhost (port 3000 for frontend, port 5000 for the backend). I'm having trouble implementing the user login functionality. I'll try to distill the problem here.

My main App.jsx component has a LoginScreen.jsx component as a child, which is here:

import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import AuthService from '../services/auth.js';
import './LoginScreen.css';

const LoginScreen = ({ history }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');

  useEffect(() => {
    if (localStorage.getItem('authToken')) history.push('/');
  }, [history]);

  const loginHandler = async e => {
    e.preventDefault();

    try {
      const data = { email, password };

      AuthService.loginUser(data).then(response => {
        localStorage.setItem('authToken', response.data.token);
      });

      history.push('/');
    } catch (error) {
      setError(error.response.data.error);

      setTimeout(() => {
        setError('');
      }, 5000);
    }
  };

  return (
    <div className="login-screen">
      <form
        className="login-screen__form"
        onSubmit={loginHandler}
        autoComplete="off"
      >
        <h3 className="login-screen__title">Login</h3>
        {error && <span className="error-message">{error}</span>}

        <div className="form-group">
          <label htmlFor="email">Email:</label>
          <input
            type="email"
            required
            id="email"
            placeholder="enter email"
            value={email}
            onChange={e => setEmail(e.target.value)}
            tabIndex={1}
          />
        </div>

        <div className="form-group">
          <label htmlFor="password">
            Password:{' '}
            <Link
              to="/forgotpassword"
              className="login-screen__forgotpassword"
              tabIndex={4}
            >
              Forgot Password?
            </Link>
          </label>
          <input
            type="password"
            required
            id="password"
            placeholder="enter password"
            value={password}
            onChange={e => setPassword(e.target.value)}
            tabIndex={2}
          />
        </div>

        <button type="submit" className="btn btn-primary" tabIndex={3}>
          Login
        </button>
        <span className="login-screen__subtext">
          Don't have an account? <Link to="/register">Register</Link>
        </span>
      </form>
    </div>
  );
};

export default LoginScreen;

When the user clicks "Login," that should trigger the AuthService, shown here:

import http from '../routing/http-auth.js';

class AuthService {
  loginUser = data => http.post('/login', data);

  registerUser = data => http.post('/register', data);

  userForgotPassword = data => http.post('/forgotpassword', data);

  userPasswordReset = data => http.put('/passwordreset/:resetToken', data);
}

export default new AuthService();

The http in the above file is an axios instance, shown below.

import axios from 'axios';

export default axios.create({
  baseURL: 'http://localhost:5000/api/v1/auth',
  headers: {
    'Content-type': 'application/json'
  }
});

So, the request is routed to the backend, where it goes here:

import express from 'express';
import AuthController from './auth.controller.js';

const router = express.Router();

router.route('/register').post(AuthController.register);
router.route('/login').post(AuthController.login);
router.route('/forgotpassword').post(AuthController.forgotPassword);
router.route('/passwordreset/:resetToken').put(AuthController.passwordReset);

export default router;

And then here:

static login = async (req, res, next) => {
    const email = req.body.email;
    const password = req.body.password;

    if (!email || !password) {
      return next(
        new ErrorResponse('please provide an email and password', 400)
      );
    }

    try {
      const user = await User.findOne({ email }).select('+password');

      if (!user) return next(new ErrorResponse('invalid credentials', 401));

      const isMatch = await user.matchPasswords(password);

      if (!isMatch) return next(new ErrorResponse('invalid credentials', 401));

      sendToken(user, 200, res);
    } catch (error) {
      next(error);
    }
  };

This all worked on a dummy application, but now, it's not working in my note-taking app. I get the error:

Uncaught (in promise) TypeError: Cannot read property 'data' of undefined
    at loginHandler (LoginScreen.jsx:27)

How can I make this work?

Umar Khan
  • 13
  • 1
  • 4
  • Does the login work with postman? And can you add the code for sendToken method? And also can you show how you setup the server (index, app or server.js) ? – SuleymanSah Jul 25 '21 at 14:48
  • It fails in postman as well...error: please provide an email and password. – Umar Khan Jul 25 '21 at 15:13
  • Entering console.log(error) on line 25 - just above setError(error.response.data.error), I get the error: TypeError: Cannot read property 'push' of undefined at loginHandler (LoginScreen.jsx:26) – Umar Khan Jul 25 '21 at 15:18
  • Going to try some changes...thanks everyone! – Umar Khan Jul 25 '21 at 15:30
  • Umar then first try to fix the problem in express side. Did you added express json middleware so that it can read from request body? – SuleymanSah Jul 25 '21 at 15:35
  • Yes, all the required middleware is there. – Umar Khan Jul 25 '21 at 18:00
  • Does this answer your question? [How do I consume the JSON POST data in an Express application](https://stackoverflow.com/questions/10005939/how-do-i-consume-the-json-post-data-in-an-express-application) – SuleymanSah Jul 25 '21 at 18:20

1 Answers1

0

Your TypeError: Cannot read property 'data' of undefined means that your code tried to read the .data property from a undefined (null, missing) object.

At line 27 your code does setError(error.response.data.error); Therefore your error message means error exists but error.response does not.

Put console.log(error) right before that line 27 (or set a breakpoint) so you can make sure you understand the error object you catch.

It would be nice if the error message said TypeError: Cannot read property 'data' of an undefined object named error.response. But it doesn't so you must make that inference yourself.

O. Jones
  • 103,626
  • 17
  • 118
  • 172