1
votes

I am trying to load async data and use it to populate material-ui components in a form with react-hook-form. I have a TextField that seems to work fine, but I can't seem to figure out how to get the Select to show the correct value.

Here's a codesandbox to demo my problem.

I am using Controller to manage the Select as seems to be recommended in the docs:

  const { register, handleSubmit, control, reset, setValue } = useForm()

  <TextField name="name" inputRef={register} />
  <Controller
    name="color_id"
    control={control}
    register={register}
    setValue={setValue}
    as={
      <Select>
        {thingColors.map((tc, index) => (
          <MenuItem key={index} value={tc.id}>
            {tc.name}
          </MenuItem>
        ))}
      </Select>
    }
  />

I'm trying to populate the fields with reset from useForm(), which seems to work for the TextField.

  useEffect(() => {
    getData().then((result) => {
      reset({
        color_id: 3,
        name: 'Bill'
      });
    });
  }, [reset]);

This seems to correctly set the values for the form, and when I submit my form it seems to have the correct values for name and for color_id. It seems like I'm not correctly hooking up the Select and the control is not showing the selected value that I set.

How can I get my material UI Select to show my applied value here?

2

2 Answers

0
votes

Hello you can do something like this:

const Form: FC = () => {
  const { register, handleSubmit, control, reset, setValue } = useForm();
const [color, setColor] = useState({name:"", color_id:-1})
  useEffect(() => {
    getData().then((result) => {
      console.log("Got thing data", { result });
      reset({
        color_id: result.optionId,
        name: result.name
      });
      setColor( {color_id: result.optionId,
        name: result.name});
    });
  }, [reset]);

  const onSubmit = (data: any) => console.log("Form submit:", data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div style={{ width: "200px" }}>
        <div>
          <TextField
            fullWidth
            name="name"
            placeholder="Name"
            inputRef={register}
          />
        </div>
        <div>
          <Controller
            name="color_id"
            control={control}
            register={register}
            setValue={setValue}
            defaultValue={color.name}
            as={
              <Select value="name" name="color_id" fullWidth>
                {thingColors.map((tc, index) => (
                  <MenuItem key={index} value={tc.id}>
                    {tc.name}
                  </MenuItem>
                ))}
              </Select>
            }
          />
        </div>
        <p></p>
        <button type="submit">Submit</button>
      </div>
    </form>
  );
};

you can use a useState() to control your default value that you fetch with the getData() method and then pass the state to defaultValue param in the Controller.

0
votes

In the version 7 of react hook form you can use setValue() setvalue API

useEffect(() => {
  getData().then((result) => {
    setValue('color_id', '3', { shouldValidate: true })
    setValue('name', 'Bill', { shouldValidate: true })
  });
}, []);

Note than I use the shouldValidate,this is becuase I use the isValidated in the button like this:

<Button
   handler={handleSubmit(handlerSignInButton)}
   disable={!isValid || isSubmitting}
   label={"Guardar"}
 />

With shouldValidate I revalidate the inputs, There is also shouldDirty.

In the version 7 of react hook form you should use render instead of as Controller API

<Controller
control={control}
name="test"
render={({
  field: { onChange, onBlur, value, name, ref },
  fieldState: { invalid, isTouched, isDirty, error },
  formState,
}) => (
  <Checkbox
    onBlur={onBlur}
    onChange={onChange}
    checked={value}
    inputRef={ref}
  />
)}

/>

Or you can use reset reset API

useEffect(() => {
  getData().then((result) => {
    reset({
      'color_id': '3',
      'name': 'Bill'
     )
  });
}, []);

I have not used Material UI with react hook form, but hope this is helpful.

A example of my select component, in Ionic React Typescript:

import { ErrorMessage } from "@hookform/error-message";
import { IonItem, IonLabel, IonSelect, IonSelectOption } from 
"@ionic/react";
import { FunctionComponent } from "react";
import { Controller } from "react-hook-form";

type Opcion = {
  label: string;
  value: string;
};

interface Props {
  control: any;
  errors: any;
  defaultValue: any;
  name: string;
  label: string;
  opciones: Opcion[];
}

const Select: FunctionComponent<Props> = ({
  opciones,
  control,
  errors,
  defaultValue,
  name,
  label
  }) => {
    return (
      <>
        <IonItem className="mb-4">
          <IonLabel position="floating" color="primary">
            {label}
        </IonLabel>
        <Controller
          render={({ field: { onChange, value } }) => (
            <IonSelect
              value={value}
              onIonChange={onChange}
              interface="action-sheet"
              className="mt-2"
            >
                {opciones.map((opcion) => {
                   return (
                     <IonSelectOption value={opcion.value}
                       key={opcion.value}
                     >
                       {opcion.label}
                     </IonSelectOption>
                   );
                })}
            </IonSelect>
          )}
          control={control}
          name={name}
          defaultValue={defaultValue}
          rules={{
            required: "Este campo es obligatorio",
          }}
      />
    </IonItem>
      <ErrorMessage
        errors={errors}
        name={name}
        as={<div className="text-red-600 px-6" />}
      />
    </>
  );
};

export default Select;

And its implementation:

import React, { useEffect } from "react";
import Select from "components/Select/Select";
import { useForm } from "react-hook-form";
import Server from "server";

interface IData {
  age: String;
}

let defaultValues = {
  age: ""
}

const rulesEdad= {
  required: "Este campo es obligatorio",
}

const opcionesEdad = [
  {value: "1", label: "18-30"},
  {value: "2", label: "30-40"},
  {value: "3", label: "40-50"},
  {value: "4", label: "50+"}
]

const SelectExample: React.FC = () => {

const {
  control,
  handleSubmit,
  setValue,
  formState: { isSubmitting, isValid, errors },
} = useForm<IData>({
  defaultValues: defaultValues,
  mode: "onChange",
});

/**
 *
 * @param data
*/
const handlerButton = async (data: IData) => {
  console.log(data);
};

useEffect(() => {
 Server.getUserData()
  .then((response) => {
    setValue('age', response.age, { shouldValidate: true })
  }
}, [])

return (
  <form>
    <Select control={control} errors={errors}
      defaultValue={defaultValues.age} opciones={opcionesEdad}
      name={age} label={Edad} rules={rulesEdad}
    />
    <button
        onClick={handleSubmit(handlerSignInButton)}
        disable={!isValid || isSubmitting}
    >
      Guardar
    </button>
  </form>