繁体   English   中英

使用 react-hook-form Controller 和 Material-UI 自动完成的正确方法

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

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

TLDR:需要使用 MUI 自动完成和 react-hook-form Controller 没有defaultValue

我的自定义Autocomplete组件采用结构为{_id:'', name: ''}的 object,它显示名称并在选择选项时返回_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设置为自动完成中onChange的处理程序,并在Autocomplete中手动注册组件, useEffect所示

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

这很好用,但我不想使用useEffect挂钩,而是直接以某种方式使用寄存器。

接下来,我尝试使用react-hook-form中的Controller组件来正确注册表单中的字段,而不是使用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}
/>

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

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

HERE是一个包含这两种情况的工作沙箱。 第一个在Autocomplete中使用useEffectsetValue是有效的。 第二次尝试使用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 的不受控值 state 更改为受控。

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

更新后的沙盒在这里

任何帮助仍然非常感谢

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

在使用 react-hook-form 和 material-ui 时,这是非常有用的参考/代码框: 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(}的返回值

请注意,我将null作为defaultValue传递,因为在我的情况下,不需要此输入。 如果您保留defaultValue ,您可能会从 material-ui 库中得到一些错误。

更新:

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

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

其中errors是来自react-hook-form的 formMethods 的formMethods

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

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

首先...特别针对您的问题,您可以通过向<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>
  );
}

感谢所有其他答案,截至 2022 年 4 月 15 日,我能够弄清楚如何使其工作并在TextField组件中呈现 label:

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'}对象。 The biggest difference is the getOptionLabel , which I guess needs to account for if both when you have the list open (and option is an object) and when the option is rendered in the TextField (when option is a string) as well as when nothing被选中。

我已经让它工作得很好,包括多个标签选择器,如下所示。 它适用于 mui5 和 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"
                />
            )}
    )}
/>
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>
  );
}

我不知道为什么上述答案对我不起作用,这是对我有用的最简单的代码,我使用了render Controller 的ControlleronChange来根据所选的值更改值。

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

代替使用controller,在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"
                    />
                  )}
                />
);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM