如何通过react-hook-form使用Material UI Select?

问题描述 投票:0回答:11

我使用 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
,但也不起作用。

reactjs material-ui ref react-hook-form
11个回答
37
投票

使用 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


26
投票

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>

Codesandbox Demo


11
投票

接受的版本是正确的,但已过时。

至少在我使用的版本中:

"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,
};

6
投票

这是我的代码,可以工作,希望它能有所帮助,需要使用

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>

1
投票

这是一个使用 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>}

0
投票

✔ 我遇到了同样的问题,这就是我解决我的问题的方法:

<Select ... onChange={e => register({ name: 'academicLevel', value: e.target.value })}/>

更多信息


0
投票

当您将react-hook-form与material UI一起使用时,您不需要使用onChange和setState。仅使用 inputRef 即可正常工作!


0
投票

只需将寄存器传递给Input Ref

 <Select
   variant="outlined"
   name="reason"
   inputRef={register({ required: true })}
  >

0
投票

只需使用

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' },
            ]}
        />
    )
}

回购:https://github.com/adiathasan/mui-react-hook-form-plus

演示:https://mui-react-hook-form-plus.vercel.app/?path=/docs/


0
投票

将选择状态保存到

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;

0
投票

我调整了@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>
© www.soinside.com 2019 - 2024. All rights reserved.