React 中的表单:使用 Material UI 和 YUP 的 React Hook Forms 为什么我选择使用 React Hook Form?目标目录初始设置使用 React Hook 进行基本表单元素绑定使用 Yup 进行表单验证预填充表单字段数据 Github 仓库参考

2025-06-08

React 中的表单:使用 Material UI 和 YUP 的 React Hook Forms

为什么我选择使用 React Hook Form?

目标

目录

初始设置

使用 React Hook Form 进行基本表单元素绑定

使用 Yup 进行验证

预填充表单字段数据

Github 仓库

参考

在 React 中,有很多方法可以编写表单,有些使用 Formik、Redux Form 等库,有些则喜欢从头开始编写所有内容。使用表单库的优势在于,许多常用的表单功能都由库处理,例如验证、将整个表单数据放在一个对象中,以及减少代码编写(这一点尚有争议 :D)。React 中
的一个这样的表单库是React Hook Form。

为什么我选择使用 React Hook Form?

我尝试过几个表单库,其中最受欢迎的是Formik,但它们都没有 React Hook Form 快。在我的 Web 应用中,表单通常包含 60-70 个字段,面对如此庞大的字段数量,没有哪个表单库在性能方面能与 React Hook Form 相提并论,甚至 Formik 也不例外。

目标

在本文中,我们将介绍如何使用 React Hook Form 创建可复用的表单组件,例如 TextField、Material UI 的 Select 组件以及react-select的 MultiSelect 组件。我们将使用 Yup 进行表单验证,并介绍如何将其与 React Hook Form 集成。

在文章的最后,我将分享一个 git hub repo,其中包含了 Material UI 的所有表单组件和 React Hook Form,大家可以轻松引用或集成到自己的项目中。

目录

这篇文章很长。所以我把文章分成了几个部分。

初始设置

create-react-app我们将在本文中使用。请按照以下步骤设置基本设置

npx create-react-app hook-form-mui
cd hook-form-mui
npm install @material-ui/core @material-ui/icons react-hook-form yup @hookform/resolvers react-select styled-components @material-ui/pickers @date-io/moment@1.x moment
Enter fullscreen mode Exit fullscreen mode

安装所有软件包后,运行该应用程序一次。

npm start
Enter fullscreen mode Exit fullscreen mode

您将看到以下页面
替代文本

使用 React Hook Form 进行基本表单元素绑定

1. 文本框

在src中创建一个名为control的文件夹。在control文件夹中创建一个文件夹input。在input文件夹中创建一个文件index.js ( src -> control -> input -> index.js )

index.js将包含以下代码

import React from "react";
import { useFormContext, Controller } from "react-hook-form";
import TextField from "@material-ui/core/TextField";

function FormInput(props) {
  const { control } = useFormContext();
  const { name, label } = props;


  return (
    <Controller
      as={TextField}
      name={name}
      control={control}
      defaultValue=""
      label={label}
      fullWidth={true}
      {...props}
    />
  );
}

export default FormInput;

Enter fullscreen mode Exit fullscreen mode

让我们深入研究上面的代码。
使用React Hook Form时,需要牢记两个主要概念:

  1. 我们必须注册每个使用的表单字段。这有助于表单提交和验证。
  2. 每个表单字段都应有一个与之关联的唯一名称。

在上面的代码中,我们使用一个名为Controllerprovided 的包装组件react-hook-form来注册我们的表单字段(在本例中)TextField组件。

正如你所见,我们可以将TextField组件的附加道具和其他道具直接传递给Controller组件

 <Controller
      as={TextField}
      name={name}
      control={control}
      defaultValue=""
      label={label}
      fullWidth={true}
      InputLabelProps={{
        className: required ? "required-label" : "",
        required: required || false,
      }}
      error={isError}
      helperText={errorMessage}
      {...props}
    />
Enter fullscreen mode Exit fullscreen mode

controlobject 包含将受控组件注册到 React Hook Form 的方法。该control对象需要作为 prop 传递给Controller组件。object
control声明如下:

const { control } = useFormContext();
Enter fullscreen mode Exit fullscreen mode

在中App.js,我们将有以下代码:

import React from "react";
import { useForm, FormProvider } from "react-hook-form";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";

import FormInput from "./controls/input";

function App(props) {
  const methods = useForm();
  const { handleSubmit } = methods;

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <div style={{ padding: "10px" }}>
      <Button
        variant="contained"
        color="primary"
        onClick={handleSubmit(onSubmit)}
      >
        SUBMIT
      </Button>

      <div style={{ padding: "10px" }}>
        <FormProvider {...methods}> // pass all methods into the context
          <form>
            <Grid container spacing={2}>
              <Grid item xs={6}>
                <FormInput name="name" label="Name" />
              </Grid>
            </Grid>
          </form>
        </FormProvider>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

让我们深入研究App.js代码。
最重要的函数是 ,useForm()它是 提供的一个钩子react-hook-formuseForm()它包含表单验证、提交和表单字段注册所需的各种方法。

 const methods = useForm();
 const { handleSubmit } = methods;
Enter fullscreen mode Exit fullscreen mode

如上代码所示,useForm()提供了一个method包含handleSubmit函数的对象,该函数用于在按钮点击时提交表单。在本例中是SUBMIT按钮。

 <FormProvider {...methods}> 
          <form>
            <Grid container spacing={2}>
              <Grid item xs={6}>
                <FormInput name="name" label="Name" />
              </Grid>
            </Grid>
          </form>
        </FormProvider>
Enter fullscreen mode Exit fullscreen mode

在上面的代码块中,我们声明了一个FormProvider组件,表单及其相应的字段将在该组件下声明。此外,我们需要将所有函数和对象传递methodsFormProvider组件。这是必需的,因为我们使用了自定义表单字段的深层嵌套结构,并且组件useFormContext()中使用的FormInput需要使用methods

对于FormInput组件我们只需要传递namelabel道具。

<FormInput name="name" label="Name" />
Enter fullscreen mode Exit fullscreen mode

如果你运行该应用程序,你应该看到:
替代文本

在“名称”字段中输入任意文本,然后点击“提交”按钮。在开发控制台中检查输出。输出将是一个包含字段名称及其对应值的对象。

现在让我们以类似的方式创建其他字段组件。

2. 选择

在 src 下创建一个名为style 的新文件夹。在style文件夹下创建一个新文件index.js ( src -> style -> index.js ) index.js将包含以下代码

import styled from "styled-components";
import { InputLabel } from "@material-ui/core";

export const StyledInputLabel = styled(InputLabel)`
  && {
    .req-label {
      color: #f44336;
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

我用styled-components它来设置样式。StyledInputLabel将在下面的组件中使用FormSelect。上述样式的主要用途是在验证期间使用。

在controls创建一个名为select的新文件夹,在select文件夹内创建一个index.js文件(controls->select->index.js)。

index.js将包含以下代码

import React from "react";
import { useFormContext, Controller } from "react-hook-form";
import MenuItem from "@material-ui/core/MenuItem";
import FormControl from "@material-ui/core/FormControl";
import Select from "@material-ui/core/Select";
import InputLabel from "@material-ui/core/InputLabel";

const MuiSelect = (props) => {
  const { label, name, options } = props;

  return (
    <FormControl fullWidth={true}>
      <InputLabel htmlFor={name}>{label}</InputLabel>
      <Select id={name} {...props}>
        <MenuItem value="">
          <em>None</em>
        </MenuItem>
        {options.map((item) => (
          <MenuItem key={item.id} value={item.id}>
            {item.label}
          </MenuItem>
        ))}
      </Select>
    </FormControl>
  );
};

function FormSelect(props) {
  const { control } = useFormContext();
  const { name, label } = props;
  return (
    <React.Fragment>
      <Controller
        as={MuiSelect}
        control={control}
        name={name}
        label={label}
        defaultValue=""
        {...props}
      />
    </React.Fragment>
  );
}

export default FormSelect;
Enter fullscreen mode Exit fullscreen mode

上述代码中需要注意的事项

  1. MuiSelectfunction 是一个组件,它包含用于渲染Select字段的 UI。它主要包含三个 props namelabel以及optionsoptions它是一个对象数组,包含要在下拉列表中显示的数据。
  2. FormSelect与组件类似FormInput,我们再次使用useFormContext()方法、Controller组件和control对象。

让我们看看如何在App.jsFormSelect中使用。以下是App.js中的新代码

import React from "react";
import { useForm, FormProvider } from "react-hook-form";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";

import FormInput from "./controls/input";
import FormSelect from "./controls/select";

function App(props) {
  const methods = useForm();
  const { handleSubmit } = methods;

  const onSubmit = (data) => {
    console.log(data);
  };

  const numberData = [
    {
      id: "10",
      label: "Ten",
    },
    {
      id: "20",
      label: "Twenty",
    },
    {
      id: "30",
      label: "Thirty",
    },
  ];

  return (
    <div style={{ padding: "10px" }}>
      <Button
        variant="contained"
        color="primary"
        onClick={handleSubmit(onSubmit)}
      >
        SUBMIT
      </Button>

      <div style={{ padding: "10px" }}>
        <FormProvider {...methods}>
          <form>
            <Grid container spacing={2}>
              <Grid item xs={6}>
                <FormInput name="name" label="Name" />
              </Grid>
              <Grid item xs={6}>
                <FormSelect name="sel" label="Numbers" options={numberData} />
              </Grid>
            </Grid>
          </form>
        </FormProvider>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

App.js中发生了哪些变化

  • 我已经创建了我们将传递给的数据(对象数组)FormSelect
  const numberData = [
    {
      id: "10",
      label: "Ten",
    },
    {
      id: "20",
      label: "Twenty",
    },
    {
      id: "30",
      label: "Thirty",
    },
  ];
Enter fullscreen mode Exit fullscreen mode
  • 我已将以下代码添加到渲染中
<Grid item xs={6}>
    <FormSelect name="sel" label="Numbers" options={noData} />
</Grid>
Enter fullscreen mode Exit fullscreen mode

现在您的网页将如下所示:
替代文本

填写表单数据,然后点击“提交”按钮。在开发控制台中检查输出。

3. 自动完成多选(React-Select)

这里我们将使用最流行的 React 库之一React-Select 。在Controls创建一个名为select-autocomplete 的新文件夹,在select-autocomplete文件夹中创建两个文件index.jsindex.css文件替代文本

现在转到style文件夹下的index.js并添加以下代码:

export const StyledFormControl = styled(FormControl)`
  && {
    width: 100%;
    display: block;
    position: relative;
  }
`;

export const StyledAutoSelectInputLabel = styled(InputLabel)`
  && {
    position: relative;
    .req-label {
      color: #f44336;
    }
    transform: translate(0, 1.5px) scale(0.75);
    transform-origin: top left;
  }
`;
Enter fullscreen mode Exit fullscreen mode

现在转到select-autocomplete文件夹下的index.css并添加以下代码:

.autoselect-options {
  padding: 6px 16px;
  line-height: 1.5;
  width: auto;
  min-height: auto;
  font-size: 1rem;
  letter-spacing: 0.00938em;
  font-weight: 400;
  cursor: pointer;
}

.autoselect-options:hover {
  background-color: rgba(0, 0, 0, 0.14) !important;
}

Enter fullscreen mode Exit fullscreen mode

我进行样式更改有两个目的,首先,它将在我们添加错误处理验证时使用;其次,使 React-Select 的外观和感觉接近 Material UI Select。

现在转到select-autocomplete文件夹下的index.js并添加以下代码:

import React, { useEffect, useState } from "react";
import { useFormContext, Controller } from "react-hook-form";
import Select, { createFilter } from "react-select";
import { StyledFormControl, StyledAutoSelectInputLabel } from "../../styles";
import "./index.css";

const stylesReactSelect = {
  clearIndicator: (provided, state) => ({
    ...provided,
    cursor: "pointer",
  }),
  indicatorSeparator: (provided, state) => ({
    ...provided,
    margin: 0,
  }),
  dropdownIndicator: (provided, state) => ({
    ...provided,
    cursor: "pointer",
  }),
  placeholder: (provided, state) => ({
    ...provided,
    fontFamily: "Roboto, Helvetica, Arial, sans-serif",
    color: state.selectProps.error ? "#f44336" : "rgba(0, 0, 0, 0.54)",
  }),
  control: (provided, state) => ({
    ...provided,
    borderRadius: 0,
    border: 0,
    borderBottom: state.selectProps.error
      ? "1px solid #f44336"
      : "1px solid rgba(0,0,0,0.87)",
    boxShadow: "none",
    ":hover": {
      borderColor: state.selectProps.error ? "1px solid #f44336" : "inherit",
      boxShadow: state.selectProps.error ? "1px solid #f44336" : "none",
    },
  }),
  valueContainer: (provided, state) => ({
    ...provided,
    paddingLeft: 0,
  }),
};

const components = {
  Option,
};

function Option(props) {
  const { onMouseMove, onMouseOver, ...newInnerProps } = props.innerProps;
  return (
    <div {...newInnerProps} className="autoselect-options">
      {props.children}
    </div>
  );
}

const ReactSelect = (props) => {
  const { label, options, name } = props;
  return (
    <React.Fragment>
      <StyledFormControl>
        <StyledAutoSelectInputLabel>
          <span>{label}</span>
        </StyledAutoSelectInputLabel>
        <Select
          options={options}
          placeholder="Please Select"
          valueKey="id"
          components={components}
          isClearable={true}
          styles={stylesReactSelect}
          isSearchable={true}
          filterOption={createFilter({ ignoreAccents: false })}
          {...props}
        />
      </StyledFormControl>
    </React.Fragment>
  );
};

function FormSelectAutoComplete(props) {
  const { control } = useFormContext();
  const { name, label, options } = props;

  const [newData, setNewData] = useState([]);

  useEffect(() => {
    const newOptions = options.map((data, index) => ({
      label: data.label,
      value: data.id,
    }));
    setNewData(newOptions);
  }, [options]);

  return (
    <React.Fragment>
      <Controller
        as={ReactSelect}
        name={name}
        control={control}
        label={label}
        {...props}
        options={newData}
      />
    </React.Fragment>
  );
}

export default FormSelectAutoComplete;
Enter fullscreen mode Exit fullscreen mode

让我们分解一下代码。

const stylesReactSelect = {
  clearIndicator: (provided, state) => ({
    ...provided,
    cursor: "pointer",
  }),
  indicatorSeparator: (provided, state) => ({
    ...provided,
    margin: 0,
  }),
  dropdownIndicator: (provided, state) => ({
    ...provided,
    cursor: "pointer",
  }),
  placeholder: (provided, state) => ({
    ...provided,
    fontFamily: "Roboto, Helvetica, Arial, sans-serif",
    color: state.selectProps.error ? "#f44336" : "rgba(0, 0, 0, 0.54)",
  }),
  control: (provided, state) => ({
    ...provided,
    borderRadius: 0,
    border: 0,
    borderBottom: state.selectProps.error
      ? "1px solid #f44336"
      : "1px solid rgba(0,0,0,0.87)",
    boxShadow: "none",
    ":hover": {
      borderColor: state.selectProps.error ? "1px solid #f44336" : "inherit",
      boxShadow: state.selectProps.error ? "1px solid #f44336" : "none",
    },
  }),
  valueContainer: (provided, state) => ({
    ...provided,
    paddingLeft: 0,
  }),
};
Enter fullscreen mode Exit fullscreen mode
  • 上面的代码只是样式更改。正如我之前提到的,我这样做是为了让外观和感觉与 Material UI Select 类似,以保持设计的一致性。你可以参考链接中的React-select完整样式指南。
const components = {
  Option,
};

function Option(props) {
  const { onMouseMove, onMouseOver, ...newInnerProps } = props.innerProps;
  return (
    <div {...newInnerProps} className="autoselect-options">
      {props.children}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • 如果您有大量数据(大约 100 多个数据对象),上述代码可以提高性能
const ReactSelect = (props) => {
  const { label, options, name } = props;
  return (
    <React.Fragment>
      <StyledFormControl>
        <StyledAutoSelectInputLabel>
          <span>{label}</span>
        </StyledAutoSelectInputLabel>
        <Select
          options={options}
          placeholder="Please Select"
          valueKey="id"
          components={components}
          isClearable={true}
          styles={stylesReactSelect}
          isSearchable={true}
          filterOption={createFilter({ ignoreAccents: false })}
          {...props}
        />
      </StyledFormControl>
    </React.Fragment>
  );
};
Enter fullscreen mode Exit fullscreen mode
  • 这是带有标签和 react-select 组件的 UI 部分。与 类似FormSelect,有三个主要 props namelabeloptions。是一个对象数组,包含要在react-selectoptions中显示的数据
function FormSelectAutoComplete(props) {
  const { control } = useFormContext();
  const { name, label, options } = props;

  const [newData, setNewData] = useState([]);

  useEffect(() => {
    const newOptions = options.map((data, index) => ({
      label: data.label,
      value: data.id,
    }));
    setNewData(newOptions);
  }, [options]);

  return (
    <React.Fragment>
      <Controller
        as={ReactSelect}
        name={name}
        control={control}
        label={label}
        {...props}
        options={newData}
      />
    </React.Fragment>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • FormSelectAutoComplete类似于FormSelect组件,同样使用了useFormContext()方法、Controller组件和control对象。这里需要注意的是,传递给react-select 组件Select的数据对象数组应该包含对象中的 thiskey。在下面的代码中,我特意传递了对象中不包含 this和key 的数据(在实际场景中可能会出现这种情况),以向您展示为了满足此要求需要进行哪些更改。labelvaluelabelvalue
useEffect(() => {
    const newOptions = options.map((data, index) => ({
      label: data.label,
      value: data.id,
    }));
    setNewData(newOptions);
  }, [options]);
Enter fullscreen mode Exit fullscreen mode

label如果您的数据对象包含和value作为键,则无需执行此操作。

让我们看看如何在App.jsFormSelectAutoComplete中使用。以下是App.js中的新代码

import React from "react";
import { useForm, FormProvider } from "react-hook-form";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";

import FormInput from "./controls/input";
import FormSelect from "./controls/select";
import FormSelectAutoComplete from "./controls/select-autocomplete";

function App(props) {
  const methods = useForm();
  const { handleSubmit } = methods;

  const onSubmit = (data) => {
    console.log(data);
  };

  const numberData = [
    {
      id: "10",
      label: "Ten",
    },
    {
      id: "20",
      label: "Twenty",
    },
    {
      id: "30",
      label: "Thirty",
    },
  ];

  return (
    <div style={{ padding: "10px" }}>
      <Button
        variant="contained"
        color="primary"
        onClick={handleSubmit(onSubmit)}
      >
        SUBMIT
      </Button>

      <div style={{ padding: "10px" }}>
        <FormProvider {...methods}>
          <form>
            <Grid container spacing={2}>
              <Grid item xs={6}>
                <FormInput name="name" label="Name" />
              </Grid>
              <Grid item xs={6}>
                <FormSelect name="sel" label="Numbers" options={numberData} />
              </Grid>
              <Grid item xs={6}>
                <FormSelectAutoComplete
                  name="selAuto"
                  label="Auto Select Numbers"
                  options={numberData}
                  isMulti
                />
              </Grid>
            </Grid>
          </form>
        </FormProvider>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

改变的App.js是下面的代码

<Grid item xs={6}>
    <FormSelectAutoComplete
      name="selAuto"
      label="Auto Select Numbers"
      options={numberData}
      isMulti
    />
</Grid>
Enter fullscreen mode Exit fullscreen mode

这里我们使用了numberData与之前相同的对象数组,FormSelect因为react-select将对象数组作为我们在 prop 中传递的数据optionsisMulti如果我们想要显示多个选定的值,则使用 prop。

现在您的网页将如下所示:
替代文本

填写表单数据,然后点击“提交”按钮。在开发控制台中检查输出。

使用 Yup 进行验证

如果你有一个表单,那么 99% 的情况下你都会需要进行某种形式的验证。React Hook Forms提供了多种验证方式(基本验证模式验证)。
我们将使用Yup进行验证。

让我们修改一下App.js

import React from "react";
import { useForm, FormProvider } from "react-hook-form";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";

import FormInput from "./controls/input";
import FormSelect from "./controls/select";
import FormSelectAutoComplete from "./controls/select-autocomplete";

import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers";

const validationSchema = yup.object().shape({
  nameV: yup.string().required("Name Validation Field is Required"),
  selV: yup.string().required("Select Validation Field is Required"),
  selAutoV: yup.array().required("Multi Select Validation Field required"),
});

function App(props) {
  const methods = useForm({
    resolver: yupResolver(validationSchema),
  });
  const { handleSubmit, errors } = methods;

  const onSubmit = (data) => {
    console.log(data);
  };

  const numberData = [
    {
      id: "10",
      label: "Ten",
    },
    {
      id: "20",
      label: "Twenty",
    },
    {
      id: "30",
      label: "Thirty",
    },
  ];

  return (
    <div style={{ padding: "10px" }}>
      <Button
        variant="contained"
        color="primary"
        onClick={handleSubmit(onSubmit)}
      >
        SUBMIT
      </Button>

      <div style={{ padding: "10px" }}>
        <FormProvider {...methods}>
          <form>
            <Grid container spacing={2}>
              <Grid item xs={6}>
                <FormInput name="name" label="Name" />
              </Grid>
              <Grid item xs={6}>
                <FormInput
                  name="nameV"
                  label="Name with Validation"
                  required={true}
                  errorobj={errors}
                />
              </Grid>
              <Grid item xs={6}>
                <FormSelect name="sel" label="Numbers" options={numberData} />
              </Grid>
              <Grid item xs={6}>
                <FormSelect
                  name="selV"
                  label="Numbers with Validation"
                  options={numberData}
                  required={true}
                  errorobj={errors}
                />
              </Grid>
              <Grid item xs={6}>
                <FormSelectAutoComplete
                  name="selAuto"
                  label="Auto Select Numbers"
                  options={numberData}
                  isMulti
                />
              </Grid>
              <Grid item xs={6}>
                <FormSelectAutoComplete
                  name="selAutoV"
                  label="Auto Select Numbers with Validation"
                  options={numberData}
                  isMulti
                  required={true}
                  errorobj={errors}
                />
              </Grid>
            </Grid>
          </form>
        </FormProvider>
      </div>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

让我们分析一下新的代码变化:

import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers";
Enter fullscreen mode Exit fullscreen mode
  • 我们已经进口yupyupResolver
const validationSchema = yup.object().shape({
  nameV: yup.string().required("Name Validation Field is Required"),
  selV: yup.string().required("Select Validation Field is Required"),
  selAutoV: yup.array().required("Multi Select Validation Field required"),
});
Enter fullscreen mode Exit fullscreen mode
  • 创建一个validationSchema如上所示的对象。是需要应用验证的字段nameV的名称。用户输入值的类型为“string”,因此。由于它是必填字段。自定义错误消息可以传递给如上所示的函数。同样,是字段的名称,其中从下拉列表中选择的值的类型为“string”,因此。自定义错误消息可以传递给如上所示的函数。是字段的名称,其中所选值将采用对象数组的形式。因此。自定义错误消息可以传递给如上所示的函数。FormInputyup.string()yup.string().required()requiredselVFormSelectyup.string().required()requiredselAutoVFormSelectAutoCompleteyup.array().required()required

如果我们不传递自定义错误消息,它将不会引发错误,但会显示其他消息(试试看!)

 const methods = useForm({
    resolver: yupResolver(validationSchema),
  });
 const { handleSubmit, errors } = methods;
Enter fullscreen mode Exit fullscreen mode
  • validationSchema对象传递给yupResolver函数,如上所示。此外,我们将使用对象errors中的对象methods,该对象包含发生错误的字段以及错误消息。

  • 我们添加了三个新组件FormInputFormSelect以及FormSelectAutoComplete两个新道具required={true}errorobj={errors}

<Grid item xs={6}>
    <FormInput
      name="nameV"
      label="Name with Validation"
      required={true}
      errorobj={errors}
    />
 </Grid>
Enter fullscreen mode Exit fullscreen mode
  <Grid item xs={6}>
    <FormSelect
      name="selV"
      label="Numbers with Validation"
      options={numberData}
      required={true}
      errorobj={errors}
    />
  </Grid>
Enter fullscreen mode Exit fullscreen mode
  <Grid item xs={6}>
    <FormSelectAutoComplete
      name="selAutoV"
      label="Auto Select Numbers with Validation"
      options={numberData}
      isMulti
      required={true}
      errorobj={errors}
    />
  </Grid>
Enter fullscreen mode Exit fullscreen mode

现在我们需要修改我们的FormInputFormSelect&FormSelectAutoComplete组件来突出显示验证错误并显示相应的错误消息。
FormInput

  • 在控件输入文件夹中创建一个index.css文件控件 -> 输入 -> index.css 。index.css将包含以下代码:
.required-label span {
    color: #f44336;
  }
Enter fullscreen mode Exit fullscreen mode
import React from "react";
import { useFormContext, Controller } from "react-hook-form";
import TextField from "@material-ui/core/TextField";
import "./index.css";

function FormInput(props) {
  const { control } = useFormContext();
  const { name, label, required, errorobj } = props;
  let isError = false;
  let errorMessage = "";
  if (errorobj && errorobj.hasOwnProperty(name)) {
    isError = true;
    errorMessage = errorobj[name].message;
  }

  return (
    <Controller
      as={TextField}
      name={name}
      control={control}
      defaultValue=""
      label={label}
      fullWidth={true}
      InputLabelProps={{
        className: required ? "required-label" : "",
        required: required || false,
      }}
      error={isError}
      helperText={errorMessage}
      {...props}
    />
  );
}

export default FormInput;
Enter fullscreen mode Exit fullscreen mode

我们做出了以下更改:

const { name, label, required, errorobj } = props;
  let isError = false;
  let errorMessage = "";
  if (errorobj && errorobj.hasOwnProperty(name)) {
    isError = true;
    errorMessage = errorobj[name].message;
  }
Enter fullscreen mode Exit fullscreen mode

上面使用了作为 props 传递给组件的和。required它们由我们在验证模式中传递的字段名称和错误消息组成。此对象由React Hook Forms创建。上面的代码片段在我们创建的和表单组件中类似。errorobjFormInputApp.jserrorObjFormSelectFormSelectAutoComplete

我们接下来做的改变是Controller组件

    <Controller
      as={TextField}
      name={name}
      control={control}
      defaultValue=""
      label={label}
      fullWidth={true}
      InputLabelProps={{
        className: required ? "required-label" : "",
        required: required || false,
      }}
      error={isError}
      helperText={errorMessage}
      {...props}
    />
Enter fullscreen mode Exit fullscreen mode

我们向组件添加了以下新道具Controller

InputLabelProps={{
    className: required ? "required-label" : "",
    required: required || false,
}}
error={isError}
helperText={errorMessage}
Enter fullscreen mode Exit fullscreen mode

InputLabelPropsprops 由 Material UI 指定error,用于控制错误消息的样式和显示方式。helperTextTextFieldTextField

也将对组件进行类似的代码FormSelect更改FormSelectAutoComplete
FormSelect

import React from "react";
import { useFormContext, Controller } from "react-hook-form";
import MenuItem from "@material-ui/core/MenuItem";
import FormControl from "@material-ui/core/FormControl";
import Select from "@material-ui/core/Select";
import { StyledInputLabel } from "../../styles";
import FormHelperText from "@material-ui/core/FormHelperText";

const MuiSelect = (props) => {
  const { label, name, options, required, errorobj } = props;
  let isError = false;
  let errorMessage = "";
  if (errorobj && errorobj.hasOwnProperty(name)) {
    isError = true;
    errorMessage = errorobj[name].message;
  }

  return (
    <FormControl fullWidth={true} error={isError}>
      <StyledInputLabel htmlFor={name}>
        {label} {required ? <span className="req-label">*</span> : null}
      </StyledInputLabel>
      <Select id={name} {...props}>
        <MenuItem value="">
          <em>None</em>
        </MenuItem>
        {options.map((item) => (
          <MenuItem key={item.id} value={item.id}>
            {item.label}
          </MenuItem>
        ))}
      </Select>
      <FormHelperText>{errorMessage}</FormHelperText>
    </FormControl>
  );
};

function FormSelect(props) {
  const { control } = useFormContext();
  const { name, label } = props;
  return (
    <React.Fragment>
      <Controller
        as={MuiSelect}
        control={control}
        name={name}
        label={label}
        defaultValue=""
        {...props}
      />
    </React.Fragment>
  );
}

export default FormSelect;
Enter fullscreen mode Exit fullscreen mode

FormSelectAutoComplete

import React, { useEffect, useState } from "react";
import { useFormContext, Controller } from "react-hook-form";
import Select, { createFilter } from "react-select";
import { StyledFormControl, StyledAutoSelectInputLabel } from "../../styles";
import FormHelperText from "@material-ui/core/FormHelperText";
import "./index.css";

const stylesReactSelect = {
  clearIndicator: (provided, state) => ({
    ...provided,
    cursor: "pointer",
  }),
  indicatorSeparator: (provided, state) => ({
    ...provided,
    margin: 0,
  }),
  dropdownIndicator: (provided, state) => ({
    ...provided,
    cursor: "pointer",
  }),
  placeholder: (provided, state) => ({
    ...provided,
    fontFamily: "Roboto, Helvetica, Arial, sans-serif",
    color: state.selectProps.error ? "#f44336" : "rgba(0, 0, 0, 0.54)",
  }),
  control: (provided, state) => ({
    ...provided,
    borderRadius: 0,
    border: 0,
    borderBottom: state.selectProps.error
      ? "1px solid #f44336"
      : "1px solid rgba(0,0,0,0.87)",
    boxShadow: "none",
    ":hover": {
      borderColor: state.selectProps.error ? "1px solid #f44336" : "inherit",
      boxShadow: state.selectProps.error ? "1px solid #f44336" : "none",
    },
  }),
  valueContainer: (provided, state) => ({
    ...provided,
    paddingLeft: 0,
  }),
};

const components = {
  Option,
};

function Option(props) {
  const { onMouseMove, onMouseOver, ...newInnerProps } = props.innerProps;
  return (
    <div {...newInnerProps} className="autoselect-options">
      {props.children}
    </div>
  );
}

const ReactSelect = (props) => {
  const { label, options, required, errorobj, name } = props;
  let isError = false;
  let errorMessage = "";
  if (errorobj && errorobj.hasOwnProperty(name)) {
    isError = true;
    errorMessage = errorobj[name].message;
  }
  return (
    <React.Fragment>
      <StyledFormControl>
        <StyledAutoSelectInputLabel>
          <span className={isError ? "req-label" : ""}>
            {label} {required ? <span className="req-label">*</span> : null}
          </span>
        </StyledAutoSelectInputLabel>
        <Select
          options={options}
          placeholder="Please Select"
          valueKey="id"
          components={components}
          isClearable={true}
          styles={stylesReactSelect}
          isSearchable={true}
          filterOption={createFilter({ ignoreAccents: false })}
          error={isError}
          {...props}
        />
        {isError && (
          <FormHelperText error={isError}>{errorMessage}</FormHelperText>
        )}
      </StyledFormControl>
    </React.Fragment>
  );
};

function FormSelectAutoComplete(props) {
  const { control } = useFormContext();
  const { name, label, options } = props;

  const [newData, setNewData] = useState([]);

  useEffect(() => {
    const newOptions = options.map((data, index) => ({
      label: data.label,
      value: data.id,
    }));
    setNewData(newOptions);
  }, [options]);

  return (
    <React.Fragment>
      <Controller
        as={ReactSelect}
        name={name}
        control={control}
        label={label}
        defaultValue={[]}
        {...props}
        options={newData}
      />
    </React.Fragment>
  );
}

export default FormSelectAutoComplete;
Enter fullscreen mode Exit fullscreen mode

保存代码,运行应用并点击“提交”按钮。你的网页将如下所示:
替代文本

预填充表单字段数据

总会有这样一种场景,表单字段需要预先填充一些数据,例如 Web 表单的编辑案例。React
Hook Forms为我们提供了一种setValue实现此目的的方法。

setValue("name", "Ammar");
Enter fullscreen mode Exit fullscreen mode
  • 下面setValue是接受两个参数的函数。name是字段的名称,“Ammar”是要设置的字段的值。
  • setValue函数来自函数method对象useForm
 const methods = useForm();
 const {setValue} = methods;
Enter fullscreen mode Exit fullscreen mode

Github 仓库

我创建了一些表单组件,例如日期选择器单选按钮复选框,并展示了日期的验证功能。此外,本教程中的所有代码都包含在代码库中。您可以参考此代码库,也可以直接在项目中使用。
代码库

参考

如果您有任何疑问/建议,或者对我在本文中的解释有任何疑问,请在下方评论区留言。感谢您花时间阅读我的文章。
鏂囩珷鏉ユ簮锛�https://dev.to/ammartinwala52/forms-in-react-react-hook-forms-with-material-ui-and-yup-4gfb
PREV
Docker 是什么?为什么它对开发者如此重要且必不可少?第一部分
NEXT
免费测试域名