使用 react-hook-form 进行表单验证
表单是任何应用程序中必须存在的核心功能之一。此功能允许我们与用户数据(输入)进行交互,并将其处理为有价值的数据或资源(输出)。
尽管这个功能很强大,但它也有一个最大的障碍。没错,就是如何验证用户输入。这就是我写这篇文章的原因。
在本文中,我将与您分享如何使用名为的包在 React 应用程序中专门处理表单验证react-hook-form
。
让我们开始吧!
要求
- React 应用程序(全新或现有应用程序)
注意:
我将使用我之前的项目example-app
。它没有任何功能。只是一个使用 安装的全新 React 项目CRA
。
步骤
1. 添加 react-hook-form
我在里面使用 Git example-app
。因此,在添加包之前,我会创建一个名为的新分支,feat-signin
然后在完成该功能后将其合并到主分支中。
# Create and checkout to branch feat-signin
git checkout -b feat-signin
现在,是时候添加包了。
yarn add react-hook-form
2.创建表单
也许你知道我想构建什么表单。是的,这是一个登录表单。我会在用户真正登录之前验证他们的邮箱和密码。
我不会使用任何样式工具。只用 HTML 来简化和集中注意力 :)。
首先,我想在我的项目中添加一个名为 Signin 的页面。
# Here's my current project
.
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── README.md
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── pages
│ │ └── Signin.js
│ ├── reportWebVitals.js
│ └── setupTests.js
└── yarn.lock
创建登录表单并将其导入其中App.js
。
// pages/Signin.js
function Signin() {
return (
<div>
<form>
<div>
<label htmlFor="email">Email</label>
<input type="email" id="email" />
</div>
<div>
<label htmlFor="password">Password</label>
<input type="password" id="password" />
</div>
<div>
<button>Signin</button>
</div>
</form>
</div>
);
}
export default Signin;
// App.js
import Signin from './pages/Signin';
function App() {
return <Signin />;
}
export default App;
3. 使用 react-hook-form 集成表单
让我们整合之前的表单,以便我们可以收集表单内的所有用户输入。
// pages/Signin.js
import { useForm } from 'react-hook-form';
function Signin() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = (form) => {
console.log(form);
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="email">Email</label>
<input type="email" id="email" {...register('email')} />
</div>
<div>
<label htmlFor="password">Password</label>
<input type="password" id="password" {...register('password')} />
</div>
<div>
<button>Signin</button>
</div>
</form>
</div>
);
}
export default Signin;
运行应用程序,打开浏览器控制台并尝试提交表单。您将看到如下图所示的内容。
4.添加输入验证
如果我提交带有空密码的表单,我将不会收到任何表明我忘记输入密码的错误消息,这是一个糟糕的用户体验。
为了实现该功能,我们需要安装两个包。@hookform/resolvers/yup
和yup
。
yarn add @hookform/resolvers yup
注意:
yup 是 JavaScript 模式对象之一。它允许我们定义形状(结构)并验证 JavaScript 对象。您也可以使用其他模式对象,例如 Joi、Zod 等。
现在,让我们在表单中添加输入验证。
// pages/Signin.js
// ...
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
const schema = yup.object().shape({
email: yup.string().email().required(),
password: yup.string().required(),
});
function Signin() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ resolver: yupResolver(schema) });
// ...
}
// ...
现在,如果我们提交带有空密码的表单,我们将不会在浏览器控制台中看到任何消息,因为表单实际上抛出了错误消息而不是提交表单。
看,甚至光标都自动聚焦到产生错误的输入元素上。
5.显示错误消息
现在,表单可以验证输入值了,但这还不够。我们需要显示表单中存在的问题,以便用户输入正确的值。
// pages/Signin.js
<div>
<label htmlFor="email">Email</label>
<input type="email" id="email" {...register("email")} />
{errors.email?.message && <span>{errors.email.message}</span>}
</div>
<div>
<label htmlFor="password">Password</label>
<input type="password" id="password" {...register("password")} />
{errors.password?.message && <span>{errors.password.message}</span>}
</div>
嗯,我觉得错误信息不够友好,对吧?所以,我们来改进一下。
6.自定义错误消息
这就是我选择 Yup 进行模式验证的原因。我们可以轻松地像这样自定义错误消息。
// pages/Signin.js
// ...
const schema = yup.object().shape({
email: yup
.string()
.email('Please provide a valid email address')
.required('Please provide your email address'),
password: yup.string().required('Please provide your password'),
});
// ...
奖金
仅在客户端验证表单是不够的。我们还需要在服务器端验证表单,因为攻击者可以绕过我们在客户端的验证。
当我们想将服务器的错误信息显示在表单中时,问题就出现了。幸运的是,我们可以使用 react-hook-form 轻松实现此功能。
我们只需要使用setError
API 将来自服务器的错误消息显示到表单中。
// pages/Signin.js
// Response from the server
// {
// "message": "...",
// "errors": {
// email: ["The email must be a valid email address."]
// }
// }
// ...
const {
register,
handleSubmit,
formState: { errors },
setError,
} = useForm({ resolver: yupResolver(schema) });
const onSubmit = async (form) => {
// Do sign-in process. Just example :)
await Http.post(/** ... */);
if ((await Http.status()) === 422) {
let res = await Http.response();
for (let [field, messages] of Object.entries(res.errors)) {
setError(field, { type: 'manual', message: message.join(' ') });
}
}
// ...
};
// ...