Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react-hook-form handling server-side errors in handleSubmit

I'm having a hard time figuring out how to handle errors that don't necessarily pertain to a single input field in a react-hook-form. To put it differently, how do I handle handleSubmit errors?

For example, having the following form:

import to from 'await-to-js'
import axios, { AxiosResponse } from 'axios'
import React from "react"
import { useForm } from "react-hook-form"

type LoginFormData = {
  username: string,
  password: string,
}

export const Login: React.FC = () => {
  const { register, handleSubmit } = useForm<LoginFormData>()

  const onSubmit = handleSubmit(async (data) => {
    const url = '/auth/local'

    const [err, userLoginResult] = await to<AxiosResponse>(axios.post(url, data))
    if (userLoginResult) {
      alert('Login successful')
    }
    else if (err) {
      alert('Bad username or password')
    }
  })

  return (
    <div className="RegisterOrLogIn">
      <form onSubmit={onSubmit}>
        <div>
          <label htmlFor="username">username</label>
          <input name="username" id="username" ref={register} />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <input type="password" id="password" name="password" ref={register} />
        </div>
        <button type="submit"> </button>
      </form>
    </div>
  )
}

Is there a react-hook-form way of informing the user that there's an error with either the username or the password? as in, other than alert()

Perhaps this is answered elsewhere, but I could not find it.


Clarification The error received from the server does not pertain to a single field:

{
    "statusCode": 400,
    "error": "Bad Request",
    "message": [
        {
            "messages": [
                {
                    "id": "Auth.form.error.invalid",
                    "message": "Identifier or password invalid."
                }
            ]
        }
    ],
    "data": [
        {
            "messages": [
                {
                    "id": "Auth.form.error.invalid",
                    "message": "Identifier or password invalid."
                }
            ]
        }
    ]
}
like image 594
Joey Baruch Avatar asked Oct 21 '20 18:10

Joey Baruch


2 Answers

In order to display the error from the server to your user, you need to use:

  • setError to set the error programmatically when the server returns an error response.
  • errors to get the error state of every fields in your form to display to the user.
type FormInputs = {
  username: string;
};

const { setError, formState: { errors } } = useForm<FormInputs>();

In your handleSubmit callback

axios
  .post(url, data)
  .then((response) => {
    alert("Login successful");
  })
  .catch((e) => {
    const errors = e.response.data;

    if (errors.username) {
      setError('username', {
        type: "server",
        message: 'Something went wrong with username',
      });
    }
    if (errors.password) {
      setError('password', {
        type: "server",
        message: 'Something went wrong with password',
      });
    }
  });

In your component

<label htmlFor="username">username</label>
<input id="username" {...register("username")} />
<div>{errors.username && errors.username.message}</div>

Live Demo

Edit 64469861/react-hook-form-handling-errors-in-handlesubmit

like image 78
NearHuscarl Avatar answered Oct 24 '22 12:10

NearHuscarl


Inspired by @NearHuscarl's answer, I've done the following hack s.t. changes in either the username or the password inputs will remove the single error.

This hack does not scale well if your error is related to multiple fields in the form, but it worked for the login use case.

onSubmit:

const onSubmit = handleSubmit(async (data) => {
    const url = '/auth/local'

    const [err, userLoginResult] = await to<AxiosResponse>(axios.post(url, data)) // see await-to-js 
    if (userLoginResult) {
      alert('Login successful')
    }
    else if (err) {
      const formError = { type: "server", message: "Username or Password Incorrect" }
      // set same error in both:
      setError('password', formError)
      setError('username', formError)
    }
  })

component:

  return (
    <div className="RegisterOrLogIn">
      <form onSubmit={onSubmit}>
        <div>
          <label htmlFor="username">username</label>
          <input name="username" id="username" ref={register} />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <input type="password" id="password" name="password" ref={register} />
        </div>
        <div>{errors.username && errors.password?.message /*note the cross check*/}</div>
        <button type="submit"> </button>
      </form>
    </div>
  )

by setting and rendering the error on both errors.password & errors.username, the error will disappear when the user updates either of those fields.

like image 2
Joey Baruch Avatar answered Oct 24 '22 12:10

Joey Baruch