Buliding a Form
useRef is a hook for accessing DOM elements or storing mutable values that persist across renders.
It is ideal for cases where you need to interact with the DOM directly or maintain a mutable value without causing re-renders.
Key Points
Initialization : useRef(initialValue)
initializes the ref with the given value (null
in the examples).
Mutability : The current
property of the ref object is mutable, meaning it can be updated without causing a component re-render.
Persistence : The value of the ref persists across renders.
No Re-renders : Updating the ref does not trigger a component re-render.
Common Use Cases
DOM Manipulation : Accessing or modifying DOM elements directly.
Storing Values : Keeping track of mutable values like timers, previous state values, etc.
Integrating with Third-party Libraries : Using refs to work with third-party libraries that require direct DOM manipulation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 import { FormEvent, useRef } from "react";
const Form = () => {
const nameRef = useRef<HTMLInputElement>(null);
const ageRef = useRef<HTMLInputElement>(null);
const person = { name: "", age: 0 };
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
if (nameRef.current !== null) {
person.name = nameRef.current.value;
}
if (ageRef.current !== null) {
person.age = parseInt(ageRef.current.value);
}
console.log(person);
};
return (
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input ref={nameRef} id="name" type="text" className="form-control" />
</div>
<div className="mb-3">
<label htmlFor="age" className="form-label">
Age
</label>
<input ref={ageRef} id="age" type="numbear" className="form-control" />
</div>
<button className="btn btn-primary" type="submit">
Submit
</button>
</form>
);
};
export default Form;
nameRef.current' is possibly 'null'.ts(18047)
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
console.log(nameRef.current.value);
}
};
// Solution:
if (nameRef.current !== null) {
console.log(nameRef.current.value);
}
Property 'value' does not exist on type 'never'.ts(2339)
const nameRef = useRef(null);
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
if (nameRef.current !== null) {
console.log(nameRef.current.value);
}
};
// Solution:
useRef(null) => useRef<HTMLInputElement>(null);
Controlled Components
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 import { FormEvent, useRef, useState } from "react";
const Form = () => {
const [person, setPerson] = useState({ name: "", age: "" });
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
console.log(person);
};
return (
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input
id="name"
onChange={(event) =>
setPerson({ ...person, name: event.target.value })
}
value={person.name}
type="text"
className="form-control"
/>
</div>
<div className="mb-3">
<label htmlFor="age" className="form-label">
Age
</label>
<input
id="age"
onChange={(event) =>
setPerson({ ...person, age: parseInt(event.target.value) })
}
value={person.age}
type="number"
className="form-control"
/>
</div>
<button className="btn btn-primary" type="submit">
Submit
</button>
</form>
);
};
export default Form;
最基础的用法是使用 useForm
钩子来管理表单状态和提交处理,而无需添加任何验证或其他复杂功能。以下是一个最简单的例子,展示了如何使用 React Hook Form 来创建一个基本的表单:
首先,你需要安装 React Hook Form:
npm install react-hook-form
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 import React from 'react';
import { useForm } from 'react-hook-form';
const BasicForm = () => {
// 初始化 useForm 钩子
const { register, handleSubmit } = useForm();
// 定义表单提交处理函数
const onSubmit = (data: any) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<label htmlFor="name" className="form-label">Name</label>
<input
{...register("name")}
id="name"
type="text"
className="form-control"
/>
</div>
<div className="mb-3">
<label htmlFor="age" className="form-label">Age</label>
<input
{...register("age")}
id="age"
type="number"
className="form-control"
/>
</div>
<button className="btn btn-primary" type="submit">Submit</button>
</form>
);
};
export default BasicForm;
引入 useForm
钩子 :
import { useForm } from 'react-hook-form';
初始化 useForm
:
const { register, handleSubmit } = useForm();
在 React Hook Form
中,useForm
钩子用于初始化和配置表单。调用 useForm
钩子并解构出 register
和 handleSubmit
方法。
- register
:用于注册表单字段,使其受 React Hook Form 管理。
- handleSubmit
:用于处理表单提交事件。
定义表单提交处理函数 :
const onSubmit = (data: any) => {
console.log(data);
};
onSubmit
函数将在表单提交时被调用,并接收表单数据。
渲染表单 :
return (
<form onSubmit={handleSubmit(onSubmit)}>
使用 handleSubmit
包装 onSubmit
函数,确保在提交时进行处理。
渲染 Name
输入字段 :
<div className="mb-3">
<label htmlFor="name" className="form-label">Name</label>
<input
{...register("name")}
id="name"
type="text"
className="form-control"
/>
</div>
渲染 Age
输入字段 :
<div className="mb-3">
<label htmlFor="age" className="form-label">Age</label>
<input
{...register("age")}
id="age"
type="number"
className="form-control"
/>
</div>
Applying Validation
解构 fromState
方法,进而二次解构出 errors
对象
const Form = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
需要对 useForm
进行类型标注,才会有自动补全提示。
interface FormData {
name: string;
age: number;
}
const Form = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>();
register
方法输入第二个对象参数,可以使用 HTML
<div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input
{...register("name", { required: true, minLength: 3 })}
id="name"
type="text"
className="form-control"
/>
</div>
输出表单验证错误信息可以使用 errors.name?type==='required'
或 errors.name?type==='minLength'
来判断。其中 ?
是 optional chaining
,为了防止 type
属性不存在引发错误的情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 <div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input
{...register("name", { required: true, minLength: 3 })}
id="name"
type="text"
className="form-control"
/>
{errors.name?.type === "required" && (
<p className="text-danger">The name field is required.</p>
)}
{errors.name?.type === "minLength" && (
<p className="text-danger">The name must be at least 3 characters.</p>
)}
</div>
Schema based Validation with Zod
npm i zod@3.20.6
npm i @hookform/resolvers@2.9.11
使用 z.object
方法定义一个对象模式(schema)
使用 z.infer
方法从 schema
中推导出一个 TypeScript 类型(type
)FormData
。
import {z} from 'zod';
const schema = z.object({
name: z.string().min(3),
age: z.number().min(18)
})
type FormData = z.infer<typeof schema>
这样,FormData
类型将与 schema
的结构和验证规则保持一致。
当你使用 useForm
钩子时,可以传递一个配置对象来定制表单的行为。
- 告诉 React Hook Form
使用 zodResolver
来验证表单数据。
- zodResolver
将使用你定义的 schema
来验证表单字段,并将验证结果(如错误信息)传递回 React Hook Form
。
const Form = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({ resolver: zodResolver(schema) });
Zod 支持自定义错误消息以及在抛出异常时(如 NaN)的消息显示
const schema = z.object({
name: z.string().min(3, { message: "Name must be at least 3 characters." }),
age: z
.number({ invalid_type_error: "Age field is required." })
.min(18, { message: "Age must be at least 18." }),
});
输出表单错误信息可以简化为:
{errors.name && <p className="text-danger">{errors.name.message}</p>}
由于输入框获得的对象类型都是字符串,当我们需要数字类型时,需要在 register 方法中添加第二个对象参数:
<input
{...register("age", { valueAsNumber: true })}
id="age"
type="number"
className="form-control"
/>
当我们想要在表单验证不通过时无法点击提交按钮,只需要在初始化和配置表单时在解构 formState
的 isValid
对象:
const Form = () => {
const {
register,
handleSubmit,
formState: { errors, isValid },
} = useForm<FormData>({ resolver: zodResolver(schema) });
然后在提交按钮处使用 disabled={!isValid}
:
<button disabled={!isValid} className="btn btn-primary" type="submit">
这样,表单验证不通过时,按钮置灰,无法提交表单。
使用 Zod
验证表单的完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 import { FieldValues, useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({
name: z.string().min(3, { message: "Name must be at least 3 characters." }),
age: z
.number({ invalid_type_error: "Age field is required." })
.min(18, { message: "Age must be at least 18." }),
});
type FormData = z.infer<typeof schema>;
const Form = () => {
const {
register,
handleSubmit,
formState: { errors, isValid },
} = useForm<FormData>({ resolver: zodResolver(schema) });
const onSubmit = (data: FieldValues) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input
{...register("name")}
id="name"
type="text"
className="form-control"
/>
{errors.name && <p className="text-danger">{errors.name.message}</p>}
</div>
<div className="mb-3">
<label htmlFor="age" className="form-label">
Age
</label>
<input
{...register("age", { valueAsNumber: true })}
id="age"
type="number"
className="form-control"
/>
{errors.age && <p className="text-danger">{errors.age.message}</p>}
</div>
<button disabled={!isValid} className="btn btn-primary" type="submit">
Submit
</button>
</form>
);
};
export default Form;
2024-06-10 18:25 2024-06-23 23:04