hellraiser999
hellraiser999

Reputation: 91

Displaying backend response as a message to react redux frontend component

I want to show error messages like Incorrect password as an alert or toast when the user enters the wrong password. Right now, I'm only seeing those messages in the network response tab and wondering how to display a response on the frontend.

Here's login controller function:

 loginUser: async (req, res, next) => {
    try {
      console.log("inside login controller")
      const { email, password } = req.body
      if (!email || !password) {
        return res.status(400).json({ message: "Email and password are must" })
      }
      if (!validator.isEmail(email)) {
        return res.status(400).json({ message: "Invalid email" })
      }
      const user = await User.findOne({ email })
      if (!user) {
        return res.status(404).json({ message: "This email does not exist" })
      }
      if (!user.confirmPassword(password)) {
        // console.log("Password in login controller", password)
        return res.status(401).json({ message: "Incorrect password" })
      }

      res.status(200).json({ user })
    } catch (error) {
      return next(error)
    }
  }

And the login component looks like:

import React, { Component } from "react"
import validator from "validator"
import { loginUser } from "../actions/userActions"
import { connect } from "react-redux"
import { Link } from "react-router-dom"
import { toastError } from "../../utils/toastify"

class LoginForm extends Component {
  constructor(props) {
    super(props)
    this.state = {
      email: "",
      password: "",
    }
  }

  handleChange = (event) => {
    const { name, value } = event.target
    this.setState({
      [name]: value,
    })
  }

  handleSubmit = (event) => {
    event.preventDefault()
    const { email, password } = this.state

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

    if (!email || !password) {
      return toastError("Email and password are must.")
    }

    if (password.length < 6) {
      return toastError("Password must contain 6 characters.")
    }

    if (!validator.isEmail(email)) {
      return toastError("Invalid email.")
    }

    this.props.dispatch(
      loginUser(loginData, () => this.props.history.push("/"))
    )
  }

  render() {
    const isAuthInProgress = this.props.auth.isAuthInProgress
    return (
      <div>
        <div className="field">
          <p className="control has-icons-left has-icons-right">
            <input
              className="input"
              onChange={this.handleChange}
              name="email"
              value={this.state.email}
              type="email"
              placeholder="Email"
            />
            <span className="icon is-small is-left">
              <i className="fas fa-envelope"></i>
            </span>
          </p>
        </div>
        <div className="field">
          <p className="control has-icons-left">
            <input
              className="input"
              onChange={this.handleChange}
              name="password"
              value={this.state.password}
              type="password"
              placeholder="Password"
            />
            <span className="icon is-small is-left">
              <i className="fas fa-lock"></i>
            </span>
          </p>
        </div>
        <div className="field">
          <p className="control">
            {isAuthInProgress ? (
              <button className="button is-success is-loading">Login</button>
            ) : (
              <button onClick={this.handleSubmit} className="button is-success">
                Login
              </button>
            )}
          </p>
        </div>
        <Link to="/forgot-password">
          <p className="has-text-danger">Forgot password?</p>
        </Link>
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return state
}

export default connect(mapStateToProps)(LoginForm)

login action

export const loginUser = (loginData, redirect) => {
  return async (dispatch) => {
    dispatch({ type: "AUTH_STARTS" })
    try {
      const res = await axios.post(`${baseUrl}/users/login`, loginData)
      console.log(res.data)
      dispatch({
        type: "AUTH_SUCCESS",
        data: { user: res.data.user }
      })
      localStorage.setItem("authToken", res.data.token)
      redirect()
      toastSuccess("You are now logged in!")
    } catch (err) {
      console.log
      dispatch({
        type: "AUTH_ERROR",
        data: { err },
      })
    }
  }
}

auth reducer

const auth = (state = initialState, action) => {
  switch (action.type) {
    case "AUTH_STARTS":
      return {
        ...state,
        isAuthInProgress: true,
        isAuthenticated: false,
        authError: null,
      }

    case "AUTH_SUCCESS":
      return {
        ...state,
        isAuthInProgress: false,
        authError: null,
        isAuthenticated: true,
        isIdentifyingToken: false,
        user: action.data.user,
      }

    case "AUTH_ERROR":
      return {
        ...state,
        isAuthInProgress: false,
        authError: action.data.error,
        isAuthenticated: false,
        user: {},
      }

Also, is there a need for similar checks like I have done both in controller and the component like if (!email || !password) and if (!validator.isEmail(email)). Or should it be only in the backend?

Upvotes: 1

Views: 1819

Answers (1)

Ajin Kabeer
Ajin Kabeer

Reputation: 2186

Since you already have a separate state in the reducer in place authError which holds the API error response, you can leverage it by using a callback function on your loginUser dispatch.

handleSubmit = (event) => {
  /* your-validation-code */
  this.props.dispatch(loginUser(loginData, this.checkForLoginError()));
};

checkForLoginError = () => {
  const authenticationError = this.props.auth.authError;
  if (authenticationError === null) {
    this.props.history.push("/");
  } else {
    return toastError(authenticationError);
  }
};

Upvotes: 1

Related Questions