使用带有 Material-UI 自动完成功能的 react-hook-form Controller 的正确方法
Proper way to use react-hook-form Controller with Material-UI Autocomplete
我正在尝试使用自定义 Material-UI Autocomplete
组件并将其连接到 react-hook-form
。
TLDR: Need to use MUI Autocomplete with react-hook-form Controller without defaultValue
我的自定义Autocomplete
组件将对象带有结构{_id:'', name: ''}
它显示名称,return s _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
改成了return的值,但是好像不行。
在 <TextField/>
上使用 inputRef={register}
对我来说不合适,因为我想保存 _id
而不是 name
HERE 是一个包含这两种情况的工作沙箱。第一个 useEffect
和 setValue
在 Autocomplete
中起作用。第二次尝试使用 Controller
component
感谢任何帮助。
LE
在 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
任何帮助仍然非常感谢
所以,我解决了这个问题。但它揭示了我认为是自动完成中的错误。
首先……针对您的问题,您可以通过向 <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>
);
}
已接受的答案(可能)适用于有缺陷的自动完成版本。我认为该错误在之后的一段时间内得到了修复,因此可以稍微简化解决方案。
这在 reference/codesandbox 使用 react-hook-form 和 material-ui 时非常有用:https://codesandbox.io/s/react-hook-form-controller-601-j2df5?
从上面link,我修改了自动完成的例子:
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(}
的 return 值
请注意,我将 null
作为 defaultValue
传递,因为在我的情况下不需要此输入。如果您离开 defaultValue
,您可能会从 material-ui 库中得到一些错误。
更新:
根据史蒂夫评论中的问题,这就是我呈现输入的方式,以便它检查错误:
renderInput={(params) => (
<TextField
{...params}
label="Field Label"
margin="normal"
error={errors[fieldName]}
/>
)}
其中 errors
是来自 react-hook-form
的 formMethods
的对象:
const { control, watch, errors, handleSubmit } = formMethods
不使用controller,借助于register,useForm的setValue和autocomplete的value,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"
/>
)}
/>
);
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";
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>
);
}
感谢所有其他答案,截至 2022 年 4 月 15 日,我能够弄清楚如何让它工作并在 TextField
组件中呈现标签:
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'}
个对象的数组。最大的区别是 getOptionLabel
,我想如果在打开列表时(并且 option
是一个对象)和在 TextField
中呈现选项时都需要考虑到这一点(when option is a string) as well as when nothing is selected.
我正在尝试使用自定义 Material-UI Autocomplete
组件并将其连接到 react-hook-form
。
TLDR: Need to use MUI Autocomplete with react-hook-form Controller without
defaultValue
我的自定义Autocomplete
组件将对象带有结构{_id:'', name: ''}
它显示名称,return s _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
改成了return的值,但是好像不行。
在 <TextField/>
上使用 inputRef={register}
对我来说不合适,因为我想保存 _id
而不是 name
HERE 是一个包含这两种情况的工作沙箱。第一个 useEffect
和 setValue
在 Autocomplete
中起作用。第二次尝试使用 Controller
component
感谢任何帮助。
LE
在 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
任何帮助仍然非常感谢
所以,我解决了这个问题。但它揭示了我认为是自动完成中的错误。
首先……针对您的问题,您可以通过向 <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>
);
}
已接受的答案(可能)适用于有缺陷的自动完成版本。我认为该错误在之后的一段时间内得到了修复,因此可以稍微简化解决方案。
这在 reference/codesandbox 使用 react-hook-form 和 material-ui 时非常有用:https://codesandbox.io/s/react-hook-form-controller-601-j2df5?
从上面link,我修改了自动完成的例子:
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
的对象:
const { control, watch, errors, handleSubmit } = formMethods
不使用controller,借助于register,useForm的setValue和autocomplete的value,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"
/>
)}
/>
);
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";
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>
);
}
感谢所有其他答案,截至 2022 年 4 月 15 日,我能够弄清楚如何让它工作并在 TextField
组件中呈现标签:
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'}
个对象的数组。最大的区别是 getOptionLabel
,我想如果在打开列表时(并且 option
是一个对象)和在 TextField
中呈现选项时都需要考虑到这一点(when option is a string) as well as when nothing is selected.