使用Material-UI的Autocomplete组件与Formik一起

38

目前正在尝试使用Material UI的Autocomplete组件与Formik一起使用。到目前为止,像文本字段和传统选择器之类的来自Material-UI的东西都非常适合Formik。但实现Autocomplete却不是这样。Formik的onChange处理程序似乎无法更新我的city_id的值。我知道Autocomplete仍然不是Material-UI核心库的一部分,但我还在看是否有可能。

import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form } from 'formik';
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import Button from '@material-ui/core/Button';

import { cities } from '../data/cities';

import "./styles.css";

const initialValues = {
  city_id: '',
};

const submit = params => {
  alert(`Value for city_id is: ${params.city_id}`);
};

function App() {
  return (
     <Formik
      initialValues={ initialValues }
      onSubmit={ submit }
    >
      {({
        handleChange,
        values,
      }) => (
        <Form>
          <Autocomplete
            id="city_id"
            name="city_id"
            options={ cities }
            groupBy={ option => option.state }
            getOptionLabel={ option => option.name }
            style={{ width: 300 }}
            renderInput={params => (
              <TextField
                { ...params }
                onChange={ handleChange }
                margin="normal"
                label="Cities"
                fullWidth
                value={ values.city_id }
              />
            )}
          />

          <Button
            variant="contained"
            color="primary"
            type="submit"
          >
            Submit
          </Button>
        </Form>
      )}
    </Formik>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Edit angry-goldstine-8offj

4个回答

61

你的问题在于,按照你现在的做法,handleChange 不会起作用。

如果你查看一下handleChange文档

通用输入更改事件处理程序。这将更新 values[key],其中 key 是发出事件的输入的 name 属性。如果不存在 name 属性,则 handleChange 将查找输入的 id 属性。注意:此处的“输入”指所有 HTML 输入。

这应该是有效的,但问题在于 Autocomplete 中的 TextField 仅在您在其上键入某些内容时才会触发 handleChange,并且值将是文本,而不是您想要的 id 或其他属性,因此您需要将 handleChange 移动到 Autocomplete 中。

还有另一个问题,您不能在 Autocomplete 中使用 handleChange,因为它没有引用您想要的输入,并且它的参数与 input 的普通 onChange 不同,正如您可以在文档中看到的那样。

onChange
func
当值改变时触发的回调函数。
签名:
function(event: object, value: any) => void
event: 回调函数的事件源
value: null

因此,您需要使用setFieldValue并将其传递给Autocomplete,例如:

onChange={(e, value) => setFieldValue("city_id", value)}

你需要传递字段名称和想要获取的值。

这里是一个工作示例


完美!谢谢! - Carl Edwards
@CarlEdwards 您好,欢迎使用,请考虑点赞,谢谢! - Vencovsky
1
感谢详细的解释! - Carl Edwards
1
此外,在 Material 4.7.1 中的 Autocomplete 组件上有 8 个补丁,因此如果按照 Vencovsky 的准确解释后仍然存在问题,这可能是原因。 - FlipFlopSquid
虽然这个代码可以运行,但是重置选项无法正常工作。请参见此处 https://codesandbox.io/s/goofy-moon-shlr5?file=/src/index.js - Pavan Jadda
显示剩余3条评论

11

@vencovsky给出了正确答案,对我来说与Material UI 14.10.1仍然有效。

我将其补充一下,因为我的字段设置为required在使用Yup验证时。

为了使其正常工作,我有以下配置:Yup

validationSchema = {
    Yup.object().shape({
        contact: Yup.string().max(255).required('Contact is required'),
    })
}

React:

<Autocomplete
    id="contact-autocomplete"
    options={contacts}
    getOptionLabel={(contact) => `${contact?.firstName} ${contact?.lastName}`}
    onChange={(e, value) => setFieldValue("contact", value?.id || "")}
    onOpen={handleBlur}
    includeInputInList
    renderInput={(params) => (
        <TextField
            {...params}
            error={Boolean(touched.contact && errors.contact)}
            fullWidth
            helperText={touched.contact && errors.contact}
            label="Contact Person"
            name="contact"
            variant="outlined"
        />
    )}
/>

当用户点击“自动完成”元素时,它会触发“onOpen”事件,该事件运行“Formik”的“onBlur”函数并将字段标记为已触摸。 如果然后未选择项目,则Formik会标记该字段并显示“联系方式必填”验证消息。

3
在选择时,onOpen={handleBlur} 会显示“必填”错误。使用 onBlur={handleBlur} 更好,因为只有在选择/清除后才会显示错误。 - northamerican

3

您需要在Autocomplete标签中添加onChange = {(event, value) => handleChange(value)}, 如下所示:

import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form } from 'formik';
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import Button from '@material-ui/core/Button';

import { cities } from '../data/cities';

import "./styles.css";

const [cityId,setCityId]=React.useState({city_id:''});

const handleChange=(value)=>{
  // Here is the value is a selected option label or the new typed value
  setCityId({city_id:value});
}


function App() {
  return (
     <Formik
      initialValues={ cityId }
      onSubmit={() => {
        alert(`Value for city_id is: ${cityId.city_id}`);
      }}
    >
      {({
        handleChange,
        values,
      }) => (
        <Form>
          <Autocomplete
            id="city_id"
            name="city_id"
            options={ cities }
            groupBy={ option => option.state }
            getOptionLabel={ option => option.name }
            style={{ width: 300 }}
            onChange = {(event, value) => handleChange(value)}
            renderInput={params => (
              <TextField
                { ...params }
                onChange={ handleChange }
                margin="normal"
                label="Cities"
                fullWidth
                value={ values.city_id }
              />
            )}
          />

          <Button
            variant="contained"
            color="primary"
            type="submit"
          >
            Submit
          </Button>
        </Form>
      )}
    </Formik>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

如果 onChange 不起作用,您也可以使用 onInputChange。


0

最近我遇到了同样的问题并解决了。现在分享我的经验

直接在OnChange方法中更新formik值可以解决这个问题

onChange={(event, value) => (formik.values.country = value!)}

这是完整的代码
Formik设置
const formik = useFormik({
    initialValues: {
      id: user.id || "",
      name: user.name || "",
      country: user.country,
      email: user.email || "",
      submit: null,
    },
    validationSchema: Yup.object({
      email: Yup.string()
        .email("Must be a valid email")
        .max(255)
        .required("Email is required"),
      name: Yup.string().max(255).required("Name is required"),
    }),
    onSubmit: async (values, helpers): Promise<void> => {
      console.log("Updating user...");
      try {
        let userData: UserDetails = {
          id: values.id,
          email: values.email,
          name: values.name,
          country: values.country,
        };
        await userApi.registerUser(userData);
        helpers.setStatus({ success: true });
        helpers.setSubmitting(false);
        toast.success("User updated!");
      } catch (err) {
        console.error(err);
        toast.error("Something went wrong!");
        helpers.setStatus({ success: false });
        helpers.setErrors({ submit: err.message });
        helpers.setSubmitting(false);
      }
    },
  });

自动完成

            <Autocomplete
                getOptionLabel={(option): string => option.text}
                options={countries}
                value={formik.values.country}
                defaultValue={formik.values.country}
                onChange={(event, value) => (formik.values.country = value!)}
                renderInput={(params): JSX.Element => (
                  <TextField
                    {...params}
                    fullWidth
                    label="Country"
                    name="country"
                    error={Boolean(
                      formik.touched.country && formik.errors.country
                    )}
                    helperText={formik.touched.country && formik.errors.country}
                    onBlur={formik.handleBlur}
                  />
                )}
              />

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接