![](/img/trans.png)
[英]Use Material UI autocomplete in freeSolo mode with react-hook-form
[英]Proper way to use react-hook-form Controller with Material-UI Autocomplete
我正在尝试使用自定义Material-UI Autocomplete
组件并将其连接到react-hook-form
。
TLDR:需要使用 MUI 自动完成和 react-hook-form Controller 没有
defaultValue
我的自定义Autocomplete
组件采用结构为{_id:'', name: ''}
的 object,它显示名称并在选择选项时返回_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
设置为自动完成中onChange
的处理程序,并在Autocomplete
中手动注册组件, 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 Autocomplete 的工作沙箱发表评论后,我设法获得了一个功能性结果
<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 的不受控值 state 更改为受控。
我试图为它设置一个defaultValue
值,但它仍然表现得那样。 此外,由于不需要表单中的这些字段,因此我不想从选项数组中设置默认值。
更新后的沙盒在这里
任何帮助仍然非常感谢
接受的答案(可能)适用于自动完成的错误版本。 我认为该错误已在一段时间后修复,因此可以稍微简化解决方案。
在使用 react-hook-form 和 material-ui 时,这是非常有用的参考/代码框: 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 库中得到一些错误。
更新:
根据评论中的史蒂夫问题,这就是我呈现输入的方式,以便它检查错误:
renderInput={(params) => (
<TextField
{...params}
label="Field Label"
margin="normal"
error={errors[fieldName]}
/>
)}
其中errors
是来自react-hook-form
的 formMethods 的formMethods
:
const { control, watch, errors, handleSubmit } = formMethods
所以,我解决了这个问题。 但它揭示了我认为自动完成的错误。
首先...特别针对您的问题,您可以通过向<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>
);
}
感谢所有其他答案,截至 2022 年 4 月 15 日,我能够弄清楚如何使其工作并在TextField
组件中呈现 label:
const ControlledAutocomplete = ({
options,
name,
control,
defaultValue,
error,
rules,
helperText,
}) => (
<Controller
name={name}
control={control}
defaultValue={defaultValue}
rules={rules}
render={({ field }) => (
<Autocomplete
disablePortal
options={options}
getOptionLabel={(option) =>
option?.label ??
options.find(({ code }) => code === option)?.label ??
''
}
{...field}
renderInput={(params) => (
<TextField
{...params}
error={Boolean(error)}
helperText={helperText}
/>
)}
onChange={(_event, data) => field.onChange(data?.code ?? '')}
/>
)}
/>
);
ControlledAutocomplete.propTypes = {
options: PropTypes.arrayOf({
label: PropTypes.string,
code: PropTypes.string,
}),
name: PropTypes.string,
control: PropTypes.func,
defaultValue: PropTypes.string,
error: PropTypes.object,
rules: PropTypes.object,
helperText: PropTypes.string,
};
在我的例子中, options
是一组{code: 'US', label: 'United States'}
对象。 The biggest difference is the getOptionLabel
, which I guess needs to account for if both when you have the list open (and option
is an object) and when the option is rendered in the TextField
(when option is a string) as well as when nothing被选中。
我已经让它工作得很好,包括多个标签选择器,如下所示。 它适用于 mui5 和 react-hook-form 7
import { useForm, Controller } from 'react-hook-form';
import Autocomplete from '@mui/material/Autocomplete';
//setup your form and control
<Controller
control={control}
name="yourFiledSubmitName"
rules={{
required: 'required field',
}}
render={({ field: { onChange } }) => (
<Autocomplete
multiple
options={yourDataArray}
getOptionLabel={(option) => option.label}
onChange={(event, item) => {
onChange(item);
}}
renderInput={(params) => (
<TextField {...params} label="Your label" placeholder="Your placeholder"
/>
)}
)}
/>
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>
);
}
我不知道为什么上述答案对我不起作用,这是对我有用的最简单的代码,我使用了render
Controller 的Controller
和onChange
来根据所选的值更改值。
<Controller
control={control}
name="type"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange, value } }) => (
<Autocomplete
freeSolo
options={['field', 'select', 'multiple', 'date']}
onChange={(event, values) => onChange(values)}
value={value}
renderInput={(params) => (
<TextField
{...params}
label="type"
variant="outlined"
onChange={onChange}
/>
)}
/>
)}
代替使用controller,在register、setValue的useForm和value、onChange的Autocomplete我们可以达到同样的效果。
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"
/>
)}
/>
);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.