简体   繁体   中英

How to use MUI Select with react-hook-form?

I've built a form in React using MUI and React Hook Form. I'm trying to create a custom TextField element that works as a Select Input . I would like it to be an uncontrolled component with a Ref prop. I've tried to pass the inputRef prop as the MUI and React Hook Form docs recommend but with no success.

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

One thing that I've found is that if I use the native select with ref , it works just fine.

Besides, I tried to change the inputRef prop to a SelectProps one but it didn't work too.

Using Select component from Material-ui with react hook form need you to implement custom logic with a Controller https://react-hook-form.com/api#Controller

Here is a reusable component that will hopefully simplify the code to use that Select component in your app:

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;

You can use it in your app like this:

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

Here is your codeSandBox updated with this component for the selects in the Information form:

https://codesandbox.io/s/unit-multi-step-form-kgic4?file=/src/Register/Information.jsx:4406-5238

RHF v7 update

Below is a minimal code example of MUI Select in a RHF form:

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 演示

Here my code that working, hope it can help, need to use 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>

Here using native select, do not need setValue, but value alway string

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

This is an example that uses Material-UI with React hook form. You need to add the validation in 'inputRef' prop of TextField. Also you need to add 'onChange' function to keep the state updated. 'shouldValidate' will trigger the validation.

  <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>}

✔ I came across this same issue, and this is how i solved mine:

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

more info

When you using react-hook-form with material UI, you don´t need to use onChange and setState. Only use inputRef and all works!

Just need to pass the register to the Input Ref

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

Accepted version is correct but outdated.

At least in the version that I'm using: "react-hook-form": "^7.30.0" you should use the render parameter.

Here is the "updated" version that perfectly works for me:

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

The important here is to propagate the field properties down to the child element ( Select in our case)

PS. I don't think you need a separate component for it, it is pretty straight forward.

just use mui-react-hook-form-plus

Here is an example:

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM