跳转至

Buliding a Form

Accessing Input Fields#

  • 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#

  1. DOM Manipulation: Accessing or modifying DOM elements directly.
  2. Storing Values: Keeping track of mutable values like timers, previous state values, etc.
  3. 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)

1
2
3
4
5
6
7
8
9
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)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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;

Managing Forms with React Hook Form#

最基础的用法是使用 useForm 钩子来管理表单状态和提交处理,而无需添加任何验证或其他复杂功能。以下是一个最简单的例子,展示了如何使用 React Hook Form 来创建一个基本的表单:

首先,你需要安装 React Hook Form:

1
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;
  1. 引入 useForm 钩子

    1
    import { useForm } from 'react-hook-form';
    

  2. 初始化 useForm

    1
    const { register, handleSubmit } = useForm();
    

    React Hook Form 中,useForm 钩子用于初始化和配置表单。调用 useForm 钩子并解构出 registerhandleSubmit 方法。
    - register:用于注册表单字段,使其受 React Hook Form 管理。
    - handleSubmit:用于处理表单提交事件。

  3. 定义表单提交处理函数

    1
    2
    3
    const onSubmit = (data: any) => {
      console.log(data);
    };
    

  4. onSubmit 函数将在表单提交时被调用,并接收表单数据。

  5. 渲染表单

    1
    2
    return (
      <form onSubmit={handleSubmit(onSubmit)}>
    

  6. 使用 handleSubmit 包装 onSubmit 函数,确保在提交时进行处理。

  7. 渲染 Name 输入字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div className="mb-3">
      <label htmlFor="name" className="form-label">Name</label>
      <input
        {...register("name")}
        id="name"
        type="text"
        className="form-control"
      />
    </div>
    

  8. 渲染 Age 输入字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <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 对象

1
2
3
4
5
6
const Form = () => {
const {
  register,
  handleSubmit,
  formState: { errors },
} = useForm();

需要对 useForm 进行类型标注,才会有自动补全提示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
interface FormData {
  name: string;
  age: number;
}

const Form = () => {
const {
  register,
  handleSubmit,
  formState: { errors },
} = useForm<FormData>();

register 方法输入第二个对象参数,可以使用 HTML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<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 类型(typeFormData
    1
    2
    3
    4
    5
    6
    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

1
2
3
4
5
6
const Form = () => {
const {
  register,
  handleSubmit,
  formState: { errors },
} = useForm<FormData>({ resolver: zodResolver(schema) });


Zod 支持自定义错误消息以及在抛出异常时(如 NaN)的消息显示

1
2
3
4
5
6
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." }),
});

输出表单错误信息可以简化为:

1
{errors.name && <p className="text-danger">{errors.name.message}</p>}

由于输入框获得的对象类型都是字符串,当我们需要数字类型时,需要在 register 方法中添加第二个对象参数:

1
2
3
4
5
6
<input
  {...register("age", { valueAsNumber: true })}
  id="age"
  type="number"
  className="form-control"
/>

当我们想要在表单验证不通过时无法点击提交按钮,只需要在初始化和配置表单时在解构 formStateisValid 对象:

1
2
3
4
5
6
const Form = () => {
const {
  register,
  handleSubmit,
  formState: { errors, isValid },
} = useForm<FormData>({ resolver: zodResolver(schema) });

然后在提交按钮处使用 disabled={!isValid}

1
<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

评论