DEV Community

Cover image for 4 ways to use Material UI Select with React Hook Form
Matheus Raduan
Matheus Raduan

Posted on

4 ways to use Material UI Select with React Hook Form

Are you confused in how to make the Select of MaterialUI works fine with React Hooks Form?

What does not work: use directly the Select component ☹️

The most advantage of use React Hook Form is to get all the benefits of work with uncontrolled components in forms like skip re-rendering and faster mount. But there are some limitations in use some components that are created to work internally like a controlled, this is the case of component Select of Material UI. In this scenario the documentation o React Hook Form indicate to use the Controller component, but there's some other ways to do the Select component work correctly without Controller component.

Lets get started with a simple Select of persons of trinity: Father, Son and Holy Spirit.

const trinityPersons = [
    { value: "father", text: "Father" },
    { value: "son", text: "Son" },
    { value: "spirit", text: "Holy Spirit" },
];
Enter fullscreen mode Exit fullscreen mode

Let's import some things

import { FormControl, TextField, MenuItem } from "material-ui/core";
import { useForm } from "react-hooks-form";
Enter fullscreen mode Exit fullscreen mode

First way: with Textfield component

In this first way is we can save some lines of code only working directly with the TextField component and add the select prop to made our input work like an select. Now through the prop inputProps that Material UI provide to us we can add prop directly to the select input component. Magic!

The next step is check if the ref already exist to prevent error and then create our register provided by React Hook Form. This is the more shortest way because we prevent to use the Controller and Select component directly.

return(
    <FormControl>
        <TextField
          select
          label="Choose one Person of trinity"
          id="trinity"
          inputProps={{
            inputRef: (ref) => {
              if (!ref) return;
              register({
                name: "trinityPerson",
                value: ref.value,
              });
            },
          }}
        >
         {trinityPersons.map((person) => (
              <MenuItem key={person.value} value={person.value}>
                    {person.text}
                </MenuItem>
            )}
        </TextField>
    </FormControl>
);
Enter fullscreen mode Exit fullscreen mode

Second way: with Select component

This way is the same of the first way, the difference here is that we create using a more explicit syntax provided by Material UI. Now is necessary to import the Select and InputLabel component.

import { InputLabel, Select } from "@material-ui/core";
Enter fullscreen mode Exit fullscreen mode

Now we create our FormControl using the Select component:

return(
    <FormControl>
        <InputLabel htmlFor="trinity-select">
            Choose one Person of trinity
        </InputLabel>
        <Select
          id="trinity-select"
          inputProps={{
            inputRef: (ref) => {
              if (!ref) return;
              register({
                name: "trinityPerson",
                value: ref.value,
              });
            },
          }}
        >
         {trinityPersons.map((person) => (
              <MenuItem key={person.value} value={person.value}>
                    {person.text}
                </MenuItem>
            )}
        </Select>
    </FormControl>
);
Enter fullscreen mode Exit fullscreen mode

Even using the the explicit Select component we suffer with the way that this component was created, as I said before.

there are some limitations in use some components that are created to work internally like a controlled

Third way: with Controller component

This is the way that React Hook Form recommend us. Is the best approach and work like a magic, the Controller component do all the work.

One more thing to import:

import { Controller } from "react-hook-form"; 
Enter fullscreen mode Exit fullscreen mode

And do not forget do use the control of useForm hook

const { register, handleSubmit, control } = useForm();
Enter fullscreen mode Exit fullscreen mode

The function of Controller component is to wrapper the controlled component and make it easier for work with them. It was created to work with Material UI 💖

And now we create our Select component inside the Controller.

return(
<FormControl>
    <InputLabel htmlFor="trinity-select">
        Choose one Person of trinity
    </InputLabel>
    <Controller
    control={control}
    name="trinityPerson"
    as={
      <Select id="trinity-select">
          {trinityPersons.map((person) => (
                  <MenuItem key={person.value} value={person.value}>
                        {person.text}
                    </MenuItem>
                )}
      </Select>
    }
  />
</FormControl>
);
Enter fullscreen mode Exit fullscreen mode

Last way: creating a reusable component

And even exist the component way.

We create an wrapper component that abstract all the boilerplate and expose the use just like an simple Select component.

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;
Enter fullscreen mode Exit fullscreen mode

Now you can use it in your app like this:

<ReactHookFormSelect
  id="trinity-select"
  name="trinityPerson"
  label="Choose one Person of trinity"
  control={control}
>
   {trinityPersons.map((person) => (
          <MenuItem key={person.value} value={person.value}>
                {person.text}
          </MenuItem>
    )}
</ReactHookFormSelect>
Enter fullscreen mode Exit fullscreen mode

Useful links

https://github.com/react-hook-form/react-hook-form/issues/497

https://github.com/react-hook-form/react-hook-form/issues/380

https://stackoverflow.com/questions/63236951/how-to-use-material-ui-select-with-react-hook-form

https://react-hook-form.com/api#Controller

https://material-ui.com/components/text-fields/#textfield

Top comments (2)

Collapse
 
lizzal profile image
Jonas

when using the first way, i cant pre-fill any value from my hook.
always get this error:

"You have provided an out-of-range value undefined for the select (name="source") component.
Consider providing a value that matches one of the available options or ''.
The available values are 602e4ed8ff9c0648deebbaa1, 602e4ed8ff9c0648deebbaa2, 602e4ed8ff9c0648deebbaa3, 602e4ed8ff9c0648deebbaa4, 602e4ed8ff9c0648deebbaa5, 602e4ed8ff9c0648deebbaa6."

my Select looks like this:


select
id="target"
name="target"
label="Target"
variant="outlined"
value={target.id}
ref={register}
onChange={(e) => setSystem(e.target.value, 'target')}>
{systems.map((system) => (

{system.name}

))}

however, if i use a classic html select and value it the same way, it works... any idea?

Collapse
 
nafiudanlawal profile image
nafiudanlawal

There's an error in your first option's code at

{trinityPersons.map((person) => (

{person.text}

)}

it should rather be
{trinityPersons.map((person) => (

{person.text}

)
)}