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
安装所有软件包后,运行该应用程序一次。
npm start
使用 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;
让我们深入研究上面的代码。
使用React Hook Form时,需要牢记两个主要概念:
- 我们必须注册每个使用的表单字段。这有助于表单提交和验证。
- 每个表单字段都应有一个与之关联的唯一名称。
在上面的代码中,我们使用一个名为Controller
provided 的包装组件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}
/>
control
object 包含将受控组件注册到 React Hook Form 的方法。该control
对象需要作为 prop 传递给Controller
组件。objectcontrol
声明如下:
const { control } = useFormContext();
在中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;
让我们深入研究App.js
代码。
最重要的函数是 ,useForm()
它是 提供的一个钩子react-hook-form
。useForm()
它包含表单验证、提交和表单字段注册所需的各种方法。
const methods = useForm();
const { handleSubmit } = methods;
如上代码所示,useForm()
提供了一个method
包含handleSubmit
函数的对象,该函数用于在按钮点击时提交表单。在本例中是SUBMIT
按钮。
<FormProvider {...methods}>
<form>
<Grid container spacing={2}>
<Grid item xs={6}>
<FormInput name="name" label="Name" />
</Grid>
</Grid>
</form>
</FormProvider>
在上面的代码块中,我们声明了一个FormProvider
组件,表单及其相应的字段将在该组件下声明。此外,我们需要将所有函数和对象传递methods
给FormProvider
组件。这是必需的,因为我们使用了自定义表单字段的深层嵌套结构,并且组件useFormContext()
中使用的FormInput
需要使用methods
对于FormInput
组件我们只需要传递name
和label
道具。
<FormInput name="name" label="Name" />
在“名称”字段中输入任意文本,然后点击“提交”按钮。在开发控制台中检查输出。输出将是一个包含字段名称及其对应值的对象。
现在让我们以类似的方式创建其他字段组件。
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;
}
}
`;
我用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;
上述代码中需要注意的事项
MuiSelect
function 是一个组件,它包含用于渲染Select字段的 UI。它主要包含三个 propsname
,label
以及options
。options
它是一个对象数组,包含要在下拉列表中显示的数据。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;
App.js中发生了哪些变化
- 我已经创建了我们将传递给的数据(对象数组)
FormSelect
。
const numberData = [
{
id: "10",
label: "Ten",
},
{
id: "20",
label: "Twenty",
},
{
id: "30",
label: "Thirty",
},
];
- 我已将以下代码添加到渲染中
<Grid item xs={6}>
<FormSelect name="sel" label="Numbers" options={noData} />
</Grid>
填写表单数据,然后点击“提交”按钮。在开发控制台中检查输出。
3. 自动完成多选(React-Select)
这里我们将使用最流行的 React 库之一React-Select 。在Controls下创建一个名为select-autocomplete 的新文件夹,在select-autocomplete文件夹中创建两个文件index.js和index.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;
}
`;
现在转到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;
}
我进行样式更改有两个目的,首先,它将在我们添加错误处理验证时使用;其次,使 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;
让我们分解一下代码。
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,
}),
};
- 上面的代码只是样式更改。正如我之前提到的,我这样做是为了让外观和感觉与 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>
);
}
- 如果您有大量数据(大约 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>
);
};
- 这是带有标签和 react-select 组件的 UI 部分。与 类似
FormSelect
,有三个主要 propsname
,label
和options
。是一个对象数组,包含要在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>
);
}
FormSelectAutoComplete
类似于FormSelect
组件,同样使用了useFormContext()
方法、Controller
组件和control
对象。这里需要注意的是,传递给react-select 组件Select
的数据对象数组应该包含对象中的 this和key。在下面的代码中,我特意传递了对象中不包含 this和key 的数据(在实际场景中可能会出现这种情况),以向您展示为了满足此要求需要进行哪些更改。label
value
label
value
useEffect(() => {
const newOptions = options.map((data, index) => ({
label: data.label,
value: data.id,
}));
setNewData(newOptions);
}, [options]);
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;
改变的App.js
是下面的代码
<Grid item xs={6}>
<FormSelectAutoComplete
name="selAuto"
label="Auto Select Numbers"
options={numberData}
isMulti
/>
</Grid>
这里我们使用了numberData
与之前相同的对象数组,FormSelect
因为react-select将对象数组作为我们在 prop 中传递的数据options
。isMulti
如果我们想要显示多个选定的值,则使用 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;
让我们分析一下新的代码变化:
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers";
- 我们已经进口
yup
和yupResolver
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"),
});
- 创建一个
validationSchema
如上所示的对象。是需要应用验证的字段nameV
的名称。用户输入值的类型为“string”,因此。由于它是必填字段。自定义错误消息可以传递给如上所示的函数。同样,是字段的名称,其中从下拉列表中选择的值的类型为“string”,因此。自定义错误消息可以传递给如上所示的函数。是字段的名称,其中所选值将采用对象数组的形式。因此。自定义错误消息可以传递给如上所示的函数。FormInput
yup.string()
yup.string().required()
required
selV
FormSelect
yup.string().required()
required
selAutoV
FormSelectAutoComplete
yup.array().required()
required
如果我们不传递自定义错误消息,它将不会引发错误,但会显示其他消息(试试看!)
const methods = useForm({
resolver: yupResolver(validationSchema),
});
const { handleSubmit, errors } = methods;
-
将
validationSchema
对象传递给yupResolver
函数,如上所示。此外,我们将使用对象errors
中的对象methods
,该对象包含发生错误的字段以及错误消息。 -
我们添加了三个新组件
FormInput
,FormSelect
以及FormSelectAutoComplete
两个新道具required={true}
和errorobj={errors}
<Grid item xs={6}>
<FormInput
name="nameV"
label="Name with Validation"
required={true}
errorobj={errors}
/>
</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="selAutoV"
label="Auto Select Numbers with Validation"
options={numberData}
isMulti
required={true}
errorobj={errors}
/>
</Grid>
现在我们需要修改我们的FormInput
、FormSelect
&FormSelectAutoComplete
组件来突出显示验证错误并显示相应的错误消息。FormInput
- 在控件的输入文件夹中创建一个index.css文件(控件 -> 输入 -> index.css )。index.css将包含以下代码:
.required-label span {
color: #f44336;
}
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;
我们做出了以下更改:
const { name, label, required, errorobj } = props;
let isError = false;
let errorMessage = "";
if (errorobj && errorobj.hasOwnProperty(name)) {
isError = true;
errorMessage = errorobj[name].message;
}
上面使用了作为 props 传递给组件的和。required
它们由我们在验证模式中传递的字段名称和错误消息组成。此对象由React Hook Forms创建。上面的代码片段在我们创建的和表单组件中类似。errorobj
FormInput
App.js
errorObj
FormSelect
FormSelectAutoComplete
我们接下来做的改变是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}
/>
我们向组件添加了以下新道具Controller
。
InputLabelProps={{
className: required ? "required-label" : "",
required: required || false,
}}
error={isError}
helperText={errorMessage}
InputLabelProps
和props 由 Material UI 指定error
,用于控制错误消息的样式和显示方式。helperText
TextField
TextField
也将对组件进行类似的代码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;
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;
预填充表单字段数据
总会有这样一种场景,表单字段需要预先填充一些数据,例如 Web 表单的编辑案例。React
Hook Forms为我们提供了一种setValue
实现此目的的方法。
setValue("name", "Ammar");
- 下面
setValue
是接受两个参数的函数。name
是字段的名称,“Ammar”是要设置的字段的值。 setValue
函数来自函数method
对象useForm
。
const methods = useForm();
const {setValue} = methods;
Github 仓库
我创建了一些表单组件,例如日期选择器、单选按钮和复选框,并展示了日期的验证功能。此外,本教程中的所有代码都包含在代码库中。您可以参考此代码库,也可以直接在项目中使用。
代码库