我使用 Material UI 和 React Hook Form 在 React 中构建了一个表单。我正在尝试创建一个自定义
TextField
元素,用作 选择输入。我希望它是一个带有 Ref 道具的不受控制的组件。我尝试按照 Material UI 和 React Hook Form 文档的建议传递 inputRef
属性,但没有成功。
<TextField
id="id"
name="name"
select
native="true"
className={classes.textField}
label="label"
margin="normal"
variant="outlined"
inputRef={register({ required: "Choose one option" })}
error={!!errors.name}
>
<MenuItem value="">Choose one option</MenuItem>
<MenuItem value="3">03</MenuItem>
<MenuItem value="6">06</MenuItem>
<MenuItem value="9">09</MenuItem>
<MenuItem value="12">12</MenuItem>
<MenuItem value="16">16</MenuItem>
<MenuItem value="18">18</MenuItem>
</TextField>
我发现的一件事是,如果我将原生
select
与 ref
一起使用,它就可以正常工作。
此外,我尝试将
inputRef
道具更改为 SelectProps
,但也不起作用。
使用 Material UI 中的 Select 组件和 React hook 表单需要您使用控制器实现自定义逻辑https://react-hook-form.com/api#Controller
这是一个可重用的组件,有望简化在应用程序中使用该 Select 组件的代码:
import React from "react";
import FormControl from "@material-ui/core/FormControl";
import InputLabel from "@material-ui/core/InputLabel";
import Select from "@material-ui/core/Select";
import { Controller } from "react-hook-form";
const ReactHookFormSelect = ({
name,
label,
control,
defaultValue,
children,
...props
}) => {
const labelId = `${name}-label`;
return (
<FormControl {...props}>
<InputLabel id={labelId}>{label}</InputLabel>
<Controller
as={
<Select labelId={labelId} label={label}>
{children}
</Select>
}
name={name}
control={control}
defaultValue={defaultValue}
/>
</FormControl>
);
};
export default ReactHookFormSelect;
您可以在您的应用程序中使用它,如下所示:
<ReactHookFormSelect
id="numero_prestacao"
name="numero_prestacao"
className={classes.textField}
label="Em quantas parcelas?"
control={control}
defaultValue={numero_prestacao || ""}
variant="outlined"
margin="normal"
>
<MenuItem value="">Escolha uma opção</MenuItem>
<MenuItem value="3">03 parcelas</MenuItem>
<MenuItem value="6">06 parcelas</MenuItem>
<MenuItem value="9">09 parcelas</MenuItem>
<MenuItem value="12">12 parcelas</MenuItem>
<MenuItem value="16">16 parcelas</MenuItem>
<MenuItem value="18">18 parcelas</MenuItem>
</ReactHookFormSelect>
这是使用此组件更新的 codeSandBox,用于信息表单中的选择:
https://codesandbox.io/s/unit-multi-step-form-kgic4?file=/src/Register/Information.jsx:4406-5238
RHF v7 更新
下面是 RHF 形式的 Material UI
Select
的最小代码示例:
const { formState, getValues, watch, register, handleSubmit } = useForm();
const { errors } = formState;
<TextField
select
fullWidth
label="Select"
defaultValue=''
inputProps={register('currency', {
required: 'Please enter currency',
})}
error={errors.currency}
helperText={errors.currency?.message}
>
{currencies.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
接受的版本是正确的,但已过时。
至少在我使用的版本中:
"react-hook-form": "^7.30.0"
你应该使用render
参数。
这是非常适合我的“更新”版本:
<FormControl>
<InputLabel id="level-label">Level</InputLabel>
<Controller
name="level"
id="level"
defaultValue={level}
control={control}
render={({ field }) => (
<Select labelId="level-label" {...field}>
<MenuItem value={0}>0</MenuItem>
<MenuItem value={1}>1</MenuItem>
</Select>
)}
/>
<FormHelperText error={true}>{errors.level?.message}</FormHelperText>
</FormControl>
这里重要的是将
field
属性传播到子元素(在我们的例子中为 Select)
PS。我认为你不需要一个单独的组件,它非常简单。
[更新] 这是我的一个对话框的完整代码。应德山要求。
import {
Box, Chip, FormControl, Input, Stack,
} from '@mui/material';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import debounce from '../@utils/debounce';
import useRawParams from '../@utils/useRawParams';
import { useBrandsSearchQuery } from '../data/products';
import { SearchRoute } from '../SBRoutes';
import LoadingDiv from './LoadingDiv';
import SBDialog from './SBDialog';
import { useSearchBarContext } from '../contexts/SearchBarContext';
const context = { suspense: false };
/**
* Show the modal dialog with the list of brands, and search box for it
* Eeach brand will be as a link, for the SEO purposes
*/
export default function AllBrandsDialog({ open, setOpen }) {
const [t] = useTranslation();
const [query, setQuery] = useState('');
const [brands, setBrands] = useState([]);
const params = useRawParams(true);
const paramsBrands = params.brands?.split(',') || [];
const { setFilterActive } = useSearchBarContext();
const variables = useMemo(() => (query.length ? {
filterText: query,
} : null), [query]);
const [{ data, fetching: loading }] = useBrandsSearchQuery({ variables, pause: Boolean(!variables), context });
const debounceSetQuery = useCallback(debounce(200, (text) => {
setQuery(text);
}));
useEffect(() => {
if (!data || !open) return;
setBrands(data.brands || []);
}, [data, open]);
return (
<SBDialog open={open} setOpen={setOpen} title={t('Search and select a brand')}>
<Stack direction="column" spacing={2}>
<FormControl>
<Input
id="tagSearch"
placeholder={t('Start typing to see the brands')}
onChange={(e) => debounceSetQuery(e.target.value)}
autoFocus={true}
/>
</FormControl>
<Box display="grid" width={220} height={300} overflow="auto" gap={1} position="relative">
{brands?.map((brand) => (
<Chip
component={Link}
key={brand.id}
disabled={paramsBrands.indexOf(brand.url) > -1}
to={SearchRoute.generatePath({
...params,
brands: [...paramsBrands, brand.url],
page: undefined,
})}
size="small"
label={brand.nicename}
variant="outlined"
onClick={() => {
setOpen(false);
setFilterActive(false);
}}
clickable={true}
/>
))}
{loading && <LoadingDiv modal={true} />}
</Box>
</Stack>
</SBDialog>
);
}
AllBrandsDialog.propTypes = {
open: PropTypes.bool.isRequired,
setOpen: PropTypes.func.isRequired,
};
这是我的代码,可以工作,希望它能有所帮助,需要使用
setValue
<TextField
fullWidth
inputRef={register({
name: 'name',
})}
select
onChange={e => setValue('name', e.target.value, true)}
label={label}
defaultValue={defaultValue}
>
{options.map((option) => (
<MenuItem key={option.label} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
这里使用原生select,不需要setValue,但value总是字符串
<TextField
fullWidth
select
SelectProps={{
native: true,
inputProps: { ref: register, name: 'name' }
}}
label={label}
defaultValue={defaultValue}
>
{options.map((option) => (
<option key={option.label} value={option.value}>
{option.label}
</option>
))}
</TextField>
这是一个使用 Material-UI 和 React hook 形式的示例。您需要在 TextField 的 'inputRef' 属性中添加验证。此外,您还需要添加“onChange”函数以保持状态更新。 'shouldValidate' 将触发验证。
<TextField
select
name='city'
inputRef={register({ required: true })}
onChange={e => setValue('city', e.target.value, { shouldValidate: true })}
label="City"
defaultValue="">
{cityList.map((option, index) => (
<MenuItem key={index} value={option}>
{option}
</MenuItem>
))}
</TextField>
{errors.city && <ErrorText>City is required</ErrorText>}
✔ 我遇到了同样的问题,这就是我解决我的问题的方法:
<Select ... onChange={e => register({ name: 'academicLevel', value: e.target.value })}/>
当您将react-hook-form与material UI一起使用时,您不需要使用onChange和setState。仅使用 inputRef 即可正常工作!
只需将寄存器传递给Input Ref
<Select
variant="outlined"
name="reason"
inputRef={register({ required: true })}
>
只需使用
mui-react-hook-form-plus
这是一个例子:
import { HookSelect, useHookForm } from 'mui-react-hook-form-plus';
const defaultValues = {
person: {
firstName: 'Atif',
lastName: 'Aslam',
sex: '',
},
};
const App = () => {
const { registerState, handleSubmit } = useHookForm({
defaultValues,
});
const onSubmit = (_data: typeof defaultValues) => {
alert(jsonStringify(_data));
};
return (
<HookSelect
{...registerState('person.sex')}
label='SEX'
items={[
{ label: 'MALE', value: 'male' },
{ label: 'FEMALE', value: 'female' },
{ label: 'OTHERS', value: 'others' },
]}
/>
)
}
将选择状态保存到
useState
并将状态传递到 react-hook-form
寄存器
import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
import { Select, MenuItem } from '@mui/material';
const schema = yup.object().shape({
age: yup.number().required('Age is required'),
});
function App() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(schema),
});
const [age, setAge] = React.useState('');
const handleChange = (event) => {
setAge(event.target.value);
};
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
{...register('age')}
value={age}
label="Age"
onChange={handleChange}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
{errors.age && <p>{errors.age.message}</p>}
<button type="submit">Submit</button>
</form>
);
}
export default App;
我调整了@Olivier的答案,以获得适合我的情况的可行解决方案(RHF v7+,MUI v5+)
<TextField
select
{...form.register(name)}
value={form.watch(name)}
error={!!form.formState.errors[name]}
helperText={(form.formState.errors[name]?.message as React.ReactNode) ?? ''}
{...props}
>
{options.map(({ value, label }) => (
<MenuItem key={value} value={value}>
{label}
</MenuItem>
))}
</TextField>