使用带有 Material-UI 自动完成功能的 react-hook-form 控制器的正确方法
Posted
技术标签:
【中文标题】使用带有 Material-UI 自动完成功能的 react-hook-form 控制器的正确方法【英文标题】:Proper way to use react-hook-form Controller with Material-UI Autocomplete 【发布时间】:2020-08-22 14:18:43 【问题描述】:我正在尝试使用自定义 Material-UI Autocomplete
组件并将其连接到 react-hook-form
。
TLDR:需要在没有
defaultValue
的情况下将 MUI Autocomplete 与 react-hook-form 控制器一起使用
我的自定义Autocomplete
组件采用结构为_id:'', name: ''
的对象,它显示名称并在选择选项时返回_id
。 Autocomplete
工作得很好。
<Autocomplete
options=options
getOptionLabel=option => option.name
getOptionSelected=(option, value) => option._id === value._id
onChange=(event, newValue, reason) =>
handler(name, reason === 'clear' ? null : newValue._id);
renderInput=params => <TextField ...params ...inputProps />
/>
为了使其与react-hook-form
一起使用,我将setValues
设置为Autocomplete
中onChange
的处理程序,并在useEffect
中手动注册组件,如下所示
useEffect(() =>
register( name: "country1" );
,[]);
这很好用,但我不想有useEffect
钩子,而是直接以某种方式使用寄存器。
接下来我尝试使用react-hook-form
中的Controller
组件在表单中正确注册字段,而不是使用useEffect
挂钩
<Controller
name="country2"
as=
<Autocomplete
options=options
getOptionLabel=option => option.name
getOptionSelected=(option, value) => option._id === value._id
onChange=(event, newValue, reason) =>
reason === "clear" ? null : newValue._id
renderInput=params => (
<TextField ...params label="Country" />
)
/>
control=control
/>
我已将Autocomplete
组件中的onChange
更改为直接返回值,但它似乎不起作用。
在<TextField/>
上使用inputRef=register
对我来说并不合适,因为我想保存_id
而不是name
HERE 是具有两种情况的工作沙箱。第一个在Autocomplete
中使用useEffect
和setValue
有效。我第二次尝试使用Controller
组件
感谢任何帮助。
乐
Bill 使用 MUI 自动完成的工作沙箱发表评论后,我设法获得了功能结果
<Controller
name="country"
as=
<Autocomplete
options=options
getOptionLabel=option => option.name
getOptionSelected=(option, value) => option._id === value._id
renderInput=params => <TextField ...params label="Country" />
/>
onChange=([, _id ]) => _id
control=control
/>
唯一的问题是我在控制台中得到一个MUI Error
Material-UI:一个组件正在改变 Autocomplete 的非受控值状态以被控制。
我尝试为其设置defaultValue
,但它的行为仍然如此。此外,由于表单中的这些字段不是必需的,因此我不想从选项数组中设置默认值。
更新的沙盒HERE
任何帮助仍然非常感谢
【问题讨论】:
codesandbox.io/s/react-hook-form-controller-079xx你看到了吗? @Bill 谢谢你的链接,我通过它是一个工作示例,但我现在仍然面临一些与自动完成组件状态相关的其他问题。我已经用 LE 更新了这个问题。谢谢 如果你按照代码框里的内容,应该可以解决问题,对吧? codesandbox.io/s/… 【参考方案1】:接受的答案(可能)适用于自动完成的错误版本。我认为该错误已在一段时间后修复,因此可以稍微简化解决方案。
在使用 react-hook-form 和 material-ui 时,这是非常有用的参考/codesandbox:https://codesandbox.io/s/react-hook-form-controller-601-j2df5?
从上面的链接,我修改了自动完成的例子:
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
const ControlledAutocomplete = ( options = [], renderInput, getOptionLabel, onChange: ignored, control, defaultValue, name, renderOption ) =>
return (
<Controller
render=( onChange, ...props ) => (
<Autocomplete
options=options
getOptionLabel=getOptionLabel
renderOption=renderOption
renderInput=renderInput
onChange=(e, data) => onChange(data)
...props
/>
)
onChange=([, data]) => data
defaultValue=defaultValue
name=name
control=control
/>
);
配合用法:
<ControlledAutocomplete
control=control
name="inputName"
options=[ name: 'test' ]
getOptionLabel=(option) => `Option: $option.name`
renderInput=(params) => <TextField ...params label="My label" margin="normal" />
defaultValue=null
/>
control
来自useForm(
的返回值
请注意,我将null
传递为defaultValue
,因为在我的情况下,此输入不是必需的。如果您离开 defaultValue
,您可能会从 material-ui 库中得到一些错误。
更新:
根据 cmets 中的 Steve 问题,这就是我呈现输入的方式,以便它检查错误:
renderInput=(params) => (
<TextField
...params
label="Field Label"
margin="normal"
error=errors[fieldName]
/>
)
其中errors
是来自react-hook-form
的formMethods
的对象:
const control, watch, errors, handleSubmit = formMethods
【讨论】:
我很好奇您如何处理需要自动完成字段 - 对我来说,它似乎不会触发错误 @Steve - 我可以看到错误没有出现的多种原因,具体取决于:验证库和验证模式、组件结构、字段命名(例如,您需要在字段名称中使用索引如果您使用的是数组字段)。如果您提供有关您的案例的更多详细信息,我可能会调整回复。就我而言,我使用yup
进行验证,因此该字段的架构中有.required()
。我还将添加一个如何渲染输入的快速示例,以便显示该错误。如果有帮助,请告诉我。【参考方案2】:
所以,我解决了这个问题。但它揭示了我认为自动完成的错误。
首先...针对您的问题,您可以通过向<Controller>
添加一个defaultValue 来消除MUI Error
。但这只是另一轮或问题的开始。
问题在于 getOptionLabel
、getOptionSelected
和 onChange
的函数有时会传递值(即在本例中为 _id
),有时会传递选项结构 - 正如您所期望的那样。
这是我最终想出的代码:
import React from "react";
import useForm, Controller from "react-hook-form";
import TextField from "@material-ui/core";
import Autocomplete from "@material-ui/lab";
import Button from "@material-ui/core";
export default function FormTwo( options )
const register, handleSubmit, control = useForm();
const getOpObj = option =>
if (!option._id) option = options.find(op => op._id === option);
return option;
;
return (
<form onSubmit=handleSubmit(data => console.log(data))>
<Controller
name="country"
as=
<Autocomplete
options=options
getOptionLabel=option => getOpObj(option).name
getOptionSelected=(option, value) =>
return option._id === getOpObj(value)._id;
renderInput=params => <TextField ...params label="Country" />
/>
onChange=([, obj]) => getOpObj(obj)._id
control=control
defaultValue=options[0]
/>
<Button type="submit">Submit</Button>
</form>
);
【讨论】:
感谢您抽出宝贵时间回答。设置默认值对我来说不是一个选项,因为这些字段不是强制性的,因此它们必须为空。我还分叉了我的沙箱并使用您的代码进行了更新。清除选项会破坏应用程序,检查它codesandbox.io/s/busy-forest-tmb6s 这两个问题很容易解决。只需要在 getOpObj 和 getOptionSelected 中允许一个空/null 选项。然后,您可以将 null 作为 defaultOption 传递。 CodeSandbox 出于某种原因不允许我登录。不知道您可以在不登录的情况下保存分叉的沙箱。但这似乎奏效了。查看:codesandbox.io/s/angry-bogdan-w9eg8 codesandbox.io/s/…【参考方案3】:import Button from "@material-ui/core";
import Autocomplete from "@material-ui/core/Autocomplete";
import red from "@material-ui/core/colors";
import Container from "@material-ui/core/Container";
import CssBaseline from "@material-ui/core/CssBaseline";
import makeStyles from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import AdapterDateFns from "@material-ui/lab/AdapterDateFns";
import LocalizationProvider from "@material-ui/lab/LocalizationProvider";
import React, useEffect, useState from "react";
import Controller, useForm from "react-hook-form";
// const useStyles = makeStyles((theme) => (
// input:
// "&:invalid":
// borderColor: red
//
// ,
// submit:
// margin: theme.spacing(3, 0, 2)
//
// ));
export default function App()
const [itemList, setItemList] = useState([]);
// const classes = useStyles();
const
control,
handleSubmit,
setValue,
formState: errors
= useForm(
mode: "onChange",
defaultValues: item: null
);
const onSubmit = (formInputs) =>
console.log("formInputs", formInputs);
;
useEffect(() =>
setItemList([
id: 1, name: "item1" ,
id: 2, name: "item2"
]);
setValue("item", id: 3, name: "item3" );
, [setValue]);
return (
<LocalizationProvider dateAdapter=AdapterDateFns>
<Container component="main" maxWidth="xs">
<CssBaseline />
<form onSubmit=handleSubmit(onSubmit) noValidate>
<Controller
control=control
name="item"
rules= required: true
render=( field: onChange, value ) => (
<Autocomplete
onChange=(event, item) =>
onChange(item);
value=value
options=itemList
getOptionLabel=(item) => (item.name ? item.name : "")
getOptionSelected=(option, value) =>
value === undefined || value === "" || option.id === value.id
renderInput=(params) => (
<TextField
...params
label="items"
margin="normal"
variant="outlined"
error=!!errors.item
helperText=errors.item && "item required"
required
/>
)
/>
)
/>
<button
onClick=() =>
setValue("item", id: 1, name: "item1" );
>
setValue
</button>
<Button
type="submit"
fullWidth
size="large"
variant="contained"
color="primary"
// className=classes.submit
>
submit
</Button>
</form>
</Container>
</LocalizationProvider>
);
【讨论】:
codesandbox.io/s/… 为我工作。谢谢!【参考方案4】:不用控制器,借助register,useForm的setValue和value,Autocomplete的onChange我们可以达到同样的效果。
const [selectedCaste, setSelectedCaste] = useState([]);
const register, errors, setValue = useForm();
useEffect(() =>
register("caste");
, [register]);
return (
<Autocomplete
multiple
options=casteList
disableCloseOnSelect
value=selectedCaste
onChange=(_, values) =>
setSelectedCaste([...values]);
setValue("caste", [...values]);
getOptionLabel=(option) => option
renderOption=(option, selected ) => (
<React.Fragment>
<Checkbox
icon=icon
checkedIcon=checkedIcon
style= marginRight: 8
checked=selected
/>
option
</React.Fragment>
)
style= width: "100%"
renderInput=(params) => (
<TextField
...params
id="caste"
error=!!errors.caste
helperText=errors.caste?.message
variant="outlined"
label="Select caste"
placeholder="Caste"
/>
)
/>
);
【讨论】:
我们可以,但是我们改变了组件的复杂性...... 这看起来更直接,但上面提到的方法将使其更具可扩展性并且更容易在整个应用程序中重用。由于 Control 组件是在专用的ControlledAutoComplete
文件中定义的,因此我们可以将其导入到应用程序中的任何表单中,并简单地从该表单的文件中传入控件和表单特定选项。如果表单文件中的 api 调用正在检索 AutoComplete 的选项,这将特别方便。以上是关于使用带有 Material-UI 自动完成功能的 react-hook-form 控制器的正确方法的主要内容,如果未能解决你的问题,请参考以下文章
如何在 onChange 后清除 Material-ui 中的自动完成输入?
使用 React Material-UI 自动完成始终显示默认选项