reactjs 将react-hook-form控制器与Material-UI自动完成一起使用的正确方法

disho6za  于 2022-12-03  发布在  React
关注(0)|答案(7)|浏览(162)

I am trying to use a custom Material-UI Autocomplete component and connect it to react-hook-form .
TLDR: Need to use MUI 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 proper 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 that 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

mm5n2pyu

mm5n2pyu1#

接受的答案(可能)适用于有bug的自动完成版本。我认为bug在那之后的一段时间被修复了,这样解决方案就可以稍微简化了。
在使用react-hook-form和material-ui时,这是非常有用的参考/代码和框:是吗?
从上面的链接,我修改了自动完成的例子:

import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';

const ControlledAutocomplete = ({ options = [], renderInput, getOptionLabel, onChange: ignored, control, defaultValue, name, renderOption }) => {
  return (
    <Controller
      render={({ onChange, ...props }) => (
        <Autocomplete
          options={options}
          getOptionLabel={getOptionLabel}
          renderOption={renderOption}
          renderInput={renderInput}
          onChange={(e, data) => onChange(data)}
          {...props}
        />
      )}
      onChange={([, data]) => data}
      defaultValue={defaultValue}
      name={name}
      control={control}
    />
  );
}

用法:

<ControlledAutocomplete
    control={control}
    name="inputName"
    options={[{ name: 'test' }]}
    getOptionLabel={(option) => `Option: ${option.name}`}
    renderInput={(params) => <TextField {...params} label="My label" margin="normal" />}
    defaultValue={null}
/>

control来自useForm(}的返回值
注意,我将null作为defaultValue传递,因为在我的例子中,这个输入是不需要的。如果你离开defaultValue,你可能会从material-ui库中得到一些错误。

更新日期:

根据Steve在评论中提出的问题,这是我呈现输入的方式,以便它检查错误:

renderInput={(params) => (
                  <TextField
                    {...params}
                    label="Field Label"
                    margin="normal"
                    error={errors[fieldName]}
                  />
                )}

其中errorsreact-hook-formformMethods的对象:

const { control, watch, errors, handleSubmit } = formMethods
fkaflof6

fkaflof62#

所以,我修复了这个问题。但是它揭示了我认为是自动完成中的一个错误。
首先......具体到您的问题,您可以通过向<Controller>添加 defaultValue 来消除MUI Error。但这只是另一轮问题的开始。
问题在于,getOptionLabelgetOptionSelectedonChange的函数有时会传递值(即本例中的_id),有时会传递选项结构--正如您所期望的那样。
下面是我最终想到的代码:

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

qfe3c7zg3#

import { Button } from "@material-ui/core";
import Autocomplete from "@material-ui/core/Autocomplete";
import { red } from "@material-ui/core/colors";
import Container from "@material-ui/core/Container";
import CssBaseline from "@material-ui/core/CssBaseline";
import { makeStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import AdapterDateFns from "@material-ui/lab/AdapterDateFns";
import LocalizationProvider from "@material-ui/lab/LocalizationProvider";
import React, { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";

export default function App() {
  const [itemList, setItemList] = useState([]);
  // const classes = useStyles();

  const {
    control,
    handleSubmit,
    setValue,
    formState: { errors }
  } = useForm({
    mode: "onChange",
    defaultValues: { item: null }
  });

  const onSubmit = (formInputs) => {
    console.log("formInputs", formInputs);
  };

  useEffect(() => {
    setItemList([
      { id: 1, name: "item1" },
      { id: 2, name: "item2" }
    ]);
    setValue("item", { id: 3, name: "item3" });
  }, [setValue]);

  return (
    <LocalizationProvider dateAdapter={AdapterDateFns}>
      <Container component="main" maxWidth="xs">
        <CssBaseline />

        <form onSubmit={handleSubmit(onSubmit)} noValidate>
          <Controller
            control={control}
            name="item"
            rules={{ required: true }}
            render={({ field: { onChange, value } }) => (
              <Autocomplete
                onChange={(event, item) => {
                  onChange(item);
                }}
                value={value}
                options={itemList}
                getOptionLabel={(item) => (item.name ? item.name : "")}
                getOptionSelected={(option, value) =>
                  value === undefined || value === "" || option.id === value.id
                }
                renderInput={(params) => (
                  <TextField
                    {...params}
                    label="items"
                    margin="normal"
                    variant="outlined"
                    error={!!errors.item}
                    helperText={errors.item && "item required"}
                    required
                  />
                )}
              />
            )}
          />

          <button
            onClick={() => {
              setValue("item", { id: 1, name: "item1" });
            }}
          >
            setValue
          </button>

          <Button
            type="submit"
            fullWidth
            size="large"
            variant="contained"
            color="primary"
            // className={classes.submit}
          >
            submit
          </Button>
        </form>
      </Container>
    </LocalizationProvider>
  );
}
syqv5f0l

syqv5f0l4#

我不知道为什么上面的答案对我不起作用,这里是对我起作用的最简单的代码,我使用Controllerrender函数与onChange根据所选的一个来更改值。

<Controller
control={control}
name="type"
rules={{
  required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange, value } }) => (
  <Autocomplete
    freeSolo
    options={['field', 'select', 'multiple', 'date']}
    onChange={(event, values) => onChange(values)}
    value={value}
    renderInput={(params) => (
      <TextField
        {...params}
        label="type"
        variant="outlined"
        onChange={onChange}
      />
    )}
  />
)}
vsikbqxv

vsikbqxv5#

多亏了所有其他的答案,截止到2022年4月15日,我已经能够弄清楚如何让它工作,并在TextField组件中呈现标签:

const ControlledAutocomplete = ({
  options,
  name,
  control,
  defaultValue,
  error,
  rules,
  helperText,
}) => (
  <Controller
    name={name}
    control={control}
    defaultValue={defaultValue}
    rules={rules}
    render={({ field }) => (
      <Autocomplete
        disablePortal
        options={options}
        getOptionLabel={(option) =>
          option?.label ??
          options.find(({ code }) => code === option)?.label ??
          ''
        }
        {...field}
        renderInput={(params) => (
          <TextField
            {...params}
            error={Boolean(error)}
            helperText={helperText}
          />
        )}
        onChange={(_event, data) => field.onChange(data?.code ?? '')}
      />
    )}
  />
);

ControlledAutocomplete.propTypes = {
  options: PropTypes.arrayOf({
    label: PropTypes.string,
    code: PropTypes.string,
  }),
  name: PropTypes.string,
  control: PropTypes.func,
  defaultValue: PropTypes.string,
  error: PropTypes.object,
  rules: PropTypes.object,
  helperText: PropTypes.string,
};

在我的例子中,options是一个{code: 'US', label: 'United States'}对象的数组,最大的区别是getOptionLabel,我想当你打开列表(option是一个对象),当选项在TextField中呈现(当选项是一个字符串),以及当没有选择任何东西时,都需要考虑getOptionLabel

j9per5c4

j9per5c46#

我已经使它工作得很好,包括如下所示的多个标签选择器。它将与mui 5和react-hook-form 7工作得很好

import { useForm, Controller } from 'react-hook-form';
import Autocomplete from '@mui/material/Autocomplete';

//setup your form and control

<Controller
    control={control}
    name="yourFiledSubmitName"
    rules={{
        required: 'required field',
    }}
    render={({ field: { onChange } }) => (
        <Autocomplete
            multiple
            options={yourDataArray}
            getOptionLabel={(option) => option.label}
            onChange={(event, item) => {
                onChange(item);
            }}
            renderInput={(params) => (
                <TextField {...params} label="Your label" placeholder="Your placeholder"
                />
            )}
    )}
/>
cmssoen2

cmssoen27#

代替使用控制器,借助register,setValue的useForm和value,onChange的Autocomplete我们可以达到同样的结果。

const [selectedCaste, setSelectedCaste] = useState([]);
const {register, errors, setValue} = useForm();

useEffect(() => {
  register("caste");
}, [register]);

return (
                <Autocomplete
                  multiple
                  options={casteList}
                  disableCloseOnSelect
                  value={selectedCaste}
                  onChange={(_, values) => {
                    setSelectedCaste([...values]);
                    setValue("caste", [...values]);
                  }}
                  getOptionLabel={(option) => option}
                  renderOption={(option, { selected }) => (
                    <React.Fragment>
                      <Checkbox
                        icon={icon}
                        checkedIcon={checkedIcon}
                        style={{ marginRight: 8 }}
                        checked={selected}
                      />
                      {option}
                    </React.Fragment>
                  )}
                  style={{ width: "100%" }}
                  renderInput={(params) => (
                    <TextField
                      {...params}
                      id="caste"
                      error={!!errors.caste}
                      helperText={errors.caste?.message}
                      variant="outlined"
                      label="Select caste"
                      placeholder="Caste"
                    />
                  )}
                />
);

相关问题