使用带有 Material-UI 自动完成功能的 react-hook-form Controller 的正确方法

IT技术 reactjs material-ui react-hook-form
2021-05-20 17:27:45

我正在尝试使用自定义Material-UI Autocomplete组件并将其连接到react-hook-form.

TLDR:需要使用带有 react-hook-form 控制器的 MUI 自动完成而不需要 defaultValue

我的自定义Autocomplete组件采用具有结构的对象,{_id:'', name: ''}它显示名称并_id在选择选项时返回Autocomplete工作得很好。

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

为了使工作与react-hook-form我设置的setValues是处理程序onChangeAutocomplete,然后手动注册该组件在useEffect如下

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

这工作正常,但我想没有useEffect钩子,只是直接以某种方式使用寄存器。

接下来我尝试使用Controller组件 fromreact-hook-form来正确注册表单中的字段而不是使用useEffect钩子

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

我已经onChangeAutocomplete组件中的更改为直接返回值,但它似乎不起作用。

inputRef={register}在 上使用<TextField/>不会为我削减它,因为我想保存_id而不是name

HERE是包含两种情况的工作沙箱。第一个 withuseEffectsetValueinAutocomplete是有效的。第二次我尝试使用Controller组件

任何帮助表示赞赏。

在 Bill 对 MUI Autocomplete 的工作沙箱发表评论后,我设法得到了一个功能结果

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

唯一的问题是我MUI Error在控制台中得到一个

Material-UI:一个组件正在改变要控制的 Autocomplete 的不受控制的值状态。

我试图defaultValue为它设置一个,但它的行为仍然如此。此外,由于不需要表单中的这些字段,因此我不想从选项数组中设置默认值。

更新的沙箱在这里

任何帮助仍然非常感谢

4个回答

接受的答案(可能)适用于自动完成的错误版本。我认为该错误在一段时间后已修复,因此可以稍微简化解决方案。

:与react钩型和材料的UI时,这是非常有用的参考/ codesandbox https://codesandbox.io/s/react-hook-form-controller-601-j2df5

从上面的链接,我修改了自动完成示例:

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

请注意,我正在传递nulldefaultValue因为在我的情况下不需要此输入。如果你离开,defaultValue你可能会从 material-ui 库中得到一些错误。

更新:

根据评论中的史蒂夫问题,这就是我呈现输入的方式,以便它检查错误:

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

errors来自react-hook-form's的对象在哪里formMethods

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

所以,我解决了这个问题。但它揭示了我认为自动完成中的错误。

首先...特别是你的问题,您可以消除MUI Error通过添加默认值<Controller>但这只是另一轮或问题的开始。

问题是函数getOptionLabel, getOptionSelected, andonChange有时传递值(即_id本例中的the ),有时传递选项结构 - 正如您所期望的。

这是我最终想出的代码:

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>
  );
}
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";
// const useStyles = makeStyles((theme) => ({
//   input: {
//     "&:invalid": {
//       borderColor: red
//     }
//   },
//   submit: {
//     margin: theme.spacing(3, 0, 2)
//   }
// }));

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

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

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