reactjsmaterial-uireact-hook-formmui-autocomplete

Proper way to use react-hook-form Controller with Material UI Autocomplete


I am trying to use a custom Material UI Autocomplete component and connect it to react-hook-form.

TLDR: Need to use Material UI Autocomplete with react-hook-form Controller without defaultValue

My custom Autocomplete component takes an object with the structure {_id:'', name: ''} it displays the name and returns the _id when an option is selected. The Autocomplete works just fine.

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

In order to make it work with react-hook-form I've set the setValues to be the handler for onChange in the Autocomplete and manually register the component in an useEffect as follows

useEffect(() => {
  register({ name: "country1" });
},[]);

This works fine but I would like to not have the useEffect hook and just make use of the register somehow directly.

Next, I tried to use the Controller component from react-hook-form to properly register the field in the form and not to use the useEffect hook

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

I've changed the onChange in the Autocomplete component to return the value directly but it doesn't seem to work.

Using inputRef={register} on the <TextField/> would not cut it for me because I want to save the _id and not the name

HERE is a working sandbox with the two cases. The first with useEffect and setValue in the Autocomplete my works. The second my attempt in using Controller component

Any help is appreciated.

LE

After the comment from Bill with the working sandbox of MUI Autocomplete, I Managed to get a functional result

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

The only problem is that I get an MUI Error in the console

Material-UI: A component is changing the uncontrolled value state of Autocomplete to be controlled.

I've tried to set an defaultValue for it but it still behaves like that. Also, I would not want to set a default value from the options array due to the fact that these fields in the form are not required.

The updated sandbox HERE

Any help is still very much appreciated


Solution

  • So, I fixed this. But it revealed what I believe to be an error in Autocomplete.

    First... specifically to your issue, you can eliminate the MUI Error by adding a defaultValue to the <Controller>. But that was only the beginning of another round or problems.

    The problem is that functions for getOptionLabel, getOptionSelected, and onChange are sometimes passed the value (i.e. the _id in this case) and sometimes passed the option structure - as you would expect.

    Here's the code I finally came up with:

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