Reputation: 5219
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."
}
]
}
]
}
Upvotes: 40
Views: 37276
Reputation: 1964
Since v7.43.0, you can use the syntax root.xxx
error to handle server error or some global level error.
const onSubmit = () => {
try {
fetch(...)
if (response.statusCode > 300) {
setError('root.serverError', {
type: response.statusCode,
message: e.message,
// meta: {}, // something to be consider to included in the phase 2 with meta object
})
}
} catch(e) {
setError('root.serverCatch', {
type: 'server',
message: e.message
})
}
}
const onClick = () => {
setError('root.random', {
type: 'random',
message: 'random'
})
}
return (
<>
{errors.root.serverError.type === 400 && <p>server response message</p>}
<p>{errors.root.serverError.message}</p>
{errors.root.serverCatch.type === 'server' && <p>server error message</p>}
<p>{errors.root.serverCatch.message}</p>
<p>{errors.root.random.message}</p>
</>
)
Upvotes: 4
Reputation: 81310
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>
Upvotes: 45
Reputation: 5219
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.
Upvotes: 4