Formik Material UI React - 自动完成 - 不受控制到受控状态

Formik Material UI React - Autocomplete - uncontrolled to controlled state

我正在尝试了解如何遵循 Formik 的自动完成字段文档中的说明,Material UI,React 工具 here.

文档中给出的例子是:

import { Autocomplete } from 'formik-material-ui-lab';

const options = [{ title: 'The Shawshank Redemption', year: 1994 }, ...]

<Field
  name="name"
  component={Autocomplete}
  options={options}
  getOptionLabel={(option: Movie) => option.title}
  style={{ width: 300 }}
  renderInput={(params: AutocompleteRenderInputParams) => (
    <TextField
      {...params}
      error={touched['name'] && !!errors['name']}
      helperText={errors['name']}
      label="Autocomplete"
      variant="outlined"
    />
  )}
/>;

关于在 getOptionLabel 中使用 Movie 的含义,没有给出任何线索。当我尝试使用它时,Movie 和 renderInput 对象中的 AutocompleteRenderInputParams 一样带有下划线。不知道为什么。

我看过这个 ,它尝试了另一种方法,但我也无法使它起作用。

我有一个表单,其中有两个自动完成字段。目前,它看起来像这样。

当我尝试使用表单时,提交按钮挂起并且控制台日志显示:

Material-UI: The getOptionLabel method of Autocomplete returned undefined instead of a string for "".

import React, { useState } from 'react';
import { Link  } from 'react-router-dom';
import firebase, {firestore} from '../../../firebase';
import { withStyles } from '@material-ui/core/styles';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import Box from '@material-ui/core/Box';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';

import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import {
  Formik, Form, Field, ErrorMessage,
} from 'formik';
import * as Yup from 'yup';
import { Autocomplete, ToggleButtonGroup } from 'formik-material-ui-lab';
import { Switch } from 'formik-material-ui';


const styles = {

};

const allCategories = [
    {value: 'culture', label: 'Culture'},
    {value: 'other', label: 'Other'},
    
];

const sharingOptions = [
    {value: 'open', label: 'Openly'},
    
    {value: 'me', label: 'Only me'},
    
];

function Contact(props) {
  const { classes } = props;
  const [open, setOpen] = useState(false);
  const [isSubmitionCompleted, setSubmitionCompleted] = useState(false);
  
  function handleClose() {
    setOpen(false);
  }

  function handleClickOpen() {
    setSubmitionCompleted(false);
    setOpen(true);
  }

  return (
    <React.Fragment>
        <Button
            // component="button"
            color="primary"
            onClick={handleClickOpen}
            style={{ float: "right"}}
            variant="outlined"
        >
            Create an Impact Metric
        </Button>
      <Dialog
        open={open}
        onClose={handleClose}
        aria-labelledby="form-dialog-title"
      >
        {!isSubmitionCompleted &&
          <React.Fragment>
            <DialogTitle id="form-dialog-title">Create an Impact Metric</DialogTitle>
            <DialogContent>
              <DialogContentText>
                test form.
              </DialogContentText>
              <Formik
                initialValues={{ title: "", category: "",  sharing: "" }}
                
                onSubmit={(values, { setSubmitting }) => {
                   setSubmitting(true);
                   firestore.collection("testing").doc().set({
                    values,
                    createdAt: firebase.firestore.FieldValue.serverTimestamp()
                })
                
                  .then(() => {
                    setSubmitionCompleted(true);
                  });
                }}

                validationSchema={Yup.object().shape({
                  title: Yup.string()
                    .required('Required'),
                  category: Yup.string()
                    .required('Required'),
                  sharing: Yup.string()
                    .required('Required')  
                })}
              >
                {(props) => {
                  const {
                    values,
                    touched,
                    errors,
                    dirty,
                    isSubmitting,
                    handleChange,
                    handleBlur,
                    handleSubmit,
                    handleReset,
                  } = props;
                  return (
                    <form onSubmit={handleSubmit}>
                      <TextField
                        label="Title"
                        name="title"
                        className={classes.textField}
                        value={values.title}
                        onChange={handleChange}
                        onBlur={handleBlur}
                        helperText={(errors.title && touched.title) && errors.title}
                        margin="normal"
                        style={{ width: "100%"}}
                      />

                      
                      <Box margin={1}>
                        <Field
                            name="category"
                            component={Autocomplete}
                            options={allCategories}
                            getOptionLabel={option => option.label}
                            style={{ width: 300 }}
                            renderInput={(params: AutocompleteRenderInputParams) => (
                            <TextField
                                {...params}
                                error={touched['category'] && !!errors['category']}
                                helperText={
                                touched['category'] && errors['category']
                                }
                                label="Select Category"
                                variant="outlined"
                            />
                            )}
                        />
                      </Box>

                      <Box margin={1}>
                        <Field
                            name="sharing"
                            component={Autocomplete}
                            options={sharingOptions}
                            getOptionLabel={option => option.label}

                            style={{ width: 300 }}
                            renderInput={(params: AutocompleteRenderInputParams) => (
                            <TextField
                                {...params}
                                error={touched['sharing'] && !!errors['sharing']}
                                helperText={
                                touched['sharing'] && errors['sharing']
                                }
                                label="Select Sharing Option"
                                variant="outlined"
                            />
                            )}
                        />
                      </Box>
                      <DialogActions>
                        <Button
                          type="button"
                          className="outline"
                          onClick={handleReset}
                          disabled={!dirty || isSubmitting}
                        >
                          Reset
                        </Button>
                        <Button type="submit" disabled={isSubmitting}>
                          Submit
                        </Button>
                        {/* <DisplayFormikState {...props} /> */}
                      </DialogActions>
                    </form>
                  );
                }}
              </Formik>
            </DialogContent>
          </React.Fragment>
        }
        {isSubmitionCompleted &&
          <React.Fragment>
            <DialogTitle id="form-dialog-title">Thanks!</DialogTitle>
            <DialogContent>
              <DialogContentText>
                test
              </DialogContentText>
              <DialogActions>
                <Button
                  type="button"
                  className="outline"
                  onClick={handleClose}
                >
                  Close
                  </Button>
                {/* <DisplayFormikState {...props} /> */}
              </DialogActions>
            </DialogContent>
          </React.Fragment>}
      </Dialog>
    </React.Fragment>
  );
}

export default withStyles(styles)(Contact);

任何人都可以看到如何根据上面 link 发布的文档使用 formik material ui 进行自动完成吗?

我也尝试使用常规 select 表单输入。这是表单字段:

<Box margin={1}>
                        <Field
                          component={TextField}
                          type="text"
                          name="category"
                          label="Category"
                          select
                          variant="outlined"
                          helperText="Select a category"
                          margin="normal"
                          style={{ width: "100%"}}
                          InputLabelProps={{
                            shrink: true,
                          }}
                        >
                          {allCategories.map(option => (
                            <MenuItem key={option.value} value={option.value}>
                              {option.label}
                            </MenuItem>
                          ))}
                        </Field>

当我尝试此操作时,我在控制台中收到一条警告:

instrument.ts:129 Material-UI: You have provided an out-of-range value `undefined` for the select component.
Consider providing a value that matches one of the available options or ''

此警告没有任何意义 - 表单呈现时菜单已正确填充。

我也收到一条错误消息:

index.js:1 Warning: A component is changing an uncontrolled input of type undefined to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info

关于那个错误,我看到了这个 ,它建议使用值(而不是输入 - 我这样做)并将所有初始值定义为一个类型。对我来说,它们都是字符串,尽管我尝试用空数组替换 select 字段。在这两种选择中,控制台中都会返回相同的错误消息。

在这一点上 - 我不关心我使用哪个自动完成或 select,我只想让其中之一工作。

有趣的是,在这两种情况下(使用 select 和自动完成)控制台都会记录警告:

Material-UI: You have provided an out-of-range value `undefined` for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are `one`, `two`.
(anonymous) @ 0.chunk.js:141301
0.chunk.js:141301 Material-UI: You have provided an out-of-range value `undefined` for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are `a`, `b`, `c`, `d`.

但是,只有一个错误实例显示:

 A component is changing an uncontrolled input of type undefined to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: react-website -controlled-components
    in input (created by ForwardRef(SelectInput))
    in ForwardRef(SelectInput) (created by ForwardRef(InputBase))
    in div (created by ForwardRef(InputBase))
    in ForwardRef(InputBase) (created by WithStyles(ForwardRef(InputBase)))
    in Wi

此错误指向类别 select 表单输入。

我还尝试将此 code sandbox 中的性别 select 表单字段添加到我的表单中,看看是否可以正常工作。当我注释掉上述类别和共享字段,并添加默认值为空字符串的性别字段时,表单加载。

字段是:

<Field
                      name="gender"
                      label="Gender"
                      options={[
                        { value: "Male", label: "Male" },
                        { value: "Female", label: "Female" },
                        { value: "Other", label: "Other" }
                      ]}
                      component={Select}
                    />

出现 select 性别字段,但宽度约为 1 厘米,选项菜单中没有选项,我无法 select 任何内容。但是表单确实加载到 firebase,性别字段中有一个空字符串。这是进步,但还不足以继续前进。

相同的代码沙箱显示了一个使用自动完成的字段。我尝试调整它并在我的表单中使用它,如下所示:

<Field
                      name="gender"
                      label="Gender"
                      options={sharingOptions}
                      component={Autocomplete}
                      textFieldProps={{
                        label: sharingOptions.label
                      }}
                    />

当我尝试这样做时,我收到一条错误消息:

TypeError: renderInput is not a function

这条错误消息对我来说毫无意义,因为我没有在表单的任何地方使用 renderInput。

当我尝试时:

<Box margin={1}>
                        <Field
                          component={Select}
                          type="text"
                          name="category"
                          label="Impact Category"
                          select
                          variant="outlined"
                          helperText="Select a category"
                          margin="normal"
                          style={{ width: "100%"}}
                          InputLabelProps={{
                            shrink: true,
                          }}
                        >
                          {allCategories.map(option => (
                            <MenuItem key={option.value} value={option.value}>
                              {option.label}
                            </MenuItem>
                          ))}
                        </Field>
                      </Box>

我没有收到任何错误,可以保存带有选项详细信息的表格。但是,这实际上并没有解决为什么自动完成不起作用的问题。这也没有使用 linked 文档中显示的 Select 字段。所以我不清楚为什么这有效或为什么文档中显示的方法不起作用。

下一次尝试

使用此 codesandbox 中的自动完成示例作为 guide,我试过:

<Field
              name="autocomplete"
              multiple
              component={Autocomplete}
              options={sharingOptions}
              getOptionLabel={(option: any) => option.title}
              style={{width: 300}}
              renderInput={(params: AutocompleteRenderInputParams) => (
                <MuiTextField
                  {...params}
                  error={touched['autocomplete'] && !!errors['autocomplete']}
                  helperText={touched['autocomplete'] && errors['autocomplete']}
                  label="Autocomplete"
                  variant="outlined"
                />
              )}
            />

与前面的示例一样,我的代码编辑器在 getOptionLabel 中出现的值“any”下划线,它还在 AutocompleteRenderInputParams 下划线。我找不到任何文档来解释表单字段的这些元素的含义或作用。无论如何,我已经导入了 AutocompleteRenderInputParams,如代码沙箱中所示。

我将表单中自动完成字段的初始值设为空数组 - 尽管我注意到代码沙箱在此示例中未设置初始值。当我尝试删除自动完成的初始值时,我得到的错误与初始值为空数组时生成的错误相同,但我还在控制台中收到一条警告:

Warning: value for autocomplete is not an array, this can caused unexpected behaviour

当我尝试此代码时,我的控制台记录了以下错误:

TypeError: Cannot read property 'toLowerCase' of undefined

Material-UI: The getOptionLabel method of Autocomplete returned undefined instead of a string for {"value":"open","label":"Open "}.

对于其他被卡住的人来说,这很有效——尽管我不明白为什么。上面post中带下划线的错误仍然存​​在,我不知道如何解决它们。

将此分享作为一种前进的方式 - 而不是一个好的解决方案。

import React, { useState } from 'react';
import {render} from 'react-dom';

import { Link  } from 'react-router-dom';
import firebase, {firestore} from '../../../../firebase';
import { withStyles } from '@material-ui/core/styles';

import {
  Button,
  LinearProgress,
  MenuItem,
  FormControl,
  InputLabel,
  FormControlLabel,
  TextField,
  Typography,
  Box,
  Grid,
  Checkbox,
  Dialog,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
} from '@material-ui/core';
import MuiTextField from '@material-ui/core/TextField';

import ToggleButton from '@material-ui/lab/ToggleButton';
import FormatAlignLeftIcon from '@material-ui/icons/FormatAlignLeft';
import FormatAlignCenterIcon from '@material-ui/icons/FormatAlignCenter';
import FormatAlignRightIcon from '@material-ui/icons/FormatAlignRight';
import FormatAlignJustifyIcon from '@material-ui/icons/FormatAlignJustify';

import {
  Formik, Form, Field, ErrorMessage,
} from 'formik';
import * as Yup from 'yup';
// import { Autocomplete, ToggleButtonGroup } from 'formik-material-ui-lab';
import {
  Autocomplete,
  ToggleButtonGroup,
  AutocompleteRenderInputParams,
} from 'formik-material-ui-lab';
import {
  fieldToTextField,
  TextFieldProps,
  Select,
  Switch,
} from 'formik-material-ui';




const allCategories = [
    
    {value: 'one', label: 'Col'},
    {value: 'two', label: 'Com'},
    
];


function UpperCasingTextField(props: TextFieldProps) {
  const {
    form: {setFieldValue},
    field: {name},
  } = props;
  const onChange = React.useCallback(
    event => {
      const {value} = event.target;
      setFieldValue(name, value ? value.toUpperCase() : '');
    },
    [setFieldValue, name]
  );
  return <MuiTextField {...fieldToTextField(props)} onChange={onChange} />;
}



function Glossary(props) {
  const { classes } = props;
  const [open, setOpen] = useState(false);
  const [isSubmitionCompleted, setSubmitionCompleted] = useState(false);
  
  function handleClose() {
    setOpen(false);
  }

  function handleClickOpen() {
    setSubmitionCompleted(false);
    setOpen(true);
  }

  return (
    <React.Fragment>
        <Button
            // component="button"
            color="primary"
            onClick={handleClickOpen}
            style={{ float: "right"}}
            variant="outlined"
        >
            Create a Defined Term
        </Button>
      <Dialog
        open={open}
        onClose={handleClose}
        aria-labelledby="form-dialog-title"
      >
        {!isSubmitionCompleted &&
          <React.Fragment>
            <DialogTitle id="form-dialog-title">Create </DialogTitle>
            <DialogContent>
              <DialogContentText>
                
              </DialogContentText>
              <Formik
                initialValues={{ term: "",  definition: "",  category: [],  attribution: true, attributionRegion: '', context: "", relatedTerms: "", linkedTemplates: "", referenceMaterials: "" }}
                
                onSubmit={(values, { setSubmitting }) => {
                   setSubmitting(true);
                   firestore.collection("glossary").doc().set({
                    ...values,
                    createdAt: firebase.firestore.FieldValue.serverTimestamp()
                })
                
                  .then(() => {
                    setSubmitionCompleted(true);
                  });
                }}

                validationSchema={Yup.object().shape({
                  term: Yup.string()
                    .required('Required'),
                  definition: Yup.string()
                    .required('Required'),
                  category: Yup.string()
                    .required('Required'),
                  attribution: Yup.boolean()
                    .required('Required'),
                  context: Yup.string()
                    .required("Required"),
                    

                })}
              >
                {(props) => {
                  const {
                    values,
                    touched,
                    errors,
                    dirty,
                    isSubmitting,
                    handleChange,
                    handleBlur,
                    handleSubmit,
                    handleReset,
                  } = props;
                  return (
                    <form onSubmit={handleSubmit}>
                      <TextField
                        label="Term"
                        name="term"
                        className={classes.textField}
                        value={values.term}
                        onChange={handleChange}
                        onBlur={handleBlur}
                        helperText={(errors.term && touched.term) && errors.term}
                        margin="normal"
                        style={{ width: "100%"}}
                      />

                      <TextField
                        label="Meaning"
                        name="definition"
                        multiline
                        rows={4}
                        className={classes.textField}
                        value={values.definition}
                        onChange={handleChange}
                        onBlur={handleBlur}
                        helperText={(errors.definition && touched.definition) && errors.definition}
                        margin="normal"
                        style={{ width: "100%"}}
                      />

                      
                      
                      <TextField
                        label="How is it used?"
                        name="context"
                        className={classes.textField}
                        multiline
                        rows={4}
                        value={values.context}
                        onChange={handleChange}
                        onBlur={handleBlur}
                        helperText={(errors.context && touched.context) && errors.context}
                        margin="normal"
                        style={{ width: "100%"}}
                      />
                      

                      <Box margin={1}>
                        <Typography component="div" style={{ marginTop: "5vh", marginBottom: "5vh"}}>
                        Choose)?
                                <Grid component="label" container alignItems="center" spacing={1}>
                                <Grid item>Attribution</Grid>
                                <Grid item>
                                    <Field 
                                        component={Switch} 
                                        name="attribution"
                                        type="checkbox"
                                        
                                    >
                                    
                                    </Field>  
                                  </Grid>
                                <Grid item>Anonymous</Grid>
                                </Grid>
                            </Typography>
                        
                      </Box>
                      <Box margin={1}>
                        <Field
                          name="category"
                          multiple
                          component={Autocomplete}
                          options={allCategories}
                          getOptionLabel={(option: any) => option.label}
                          style={{width: 300}}
                          renderInput={(params: AutocompleteRenderInputParams) => (
                            <MuiTextField
                              {...params}
                              error={touched['autocomplete'] && !!errors['autocomplete']}
                              helperText={touched['autocomplete'] && errors['autocomplete']}
                              label="Category"
                              variant="outlined"
                            />
                          )}
                        />
                      </Box>                      
                      
                      <DialogActions>
                        <Button
                          type="button"
                          className="outline"
                          onClick={handleReset}
                          disabled={!dirty || isSubmitting}
                        >
                          Reset
                        </Button>
                        <Button type="submit" disabled={isSubmitting}>
                          Submit
                        </Button>
                        {/* <DisplayFormikState {...props} /> */}
                      </DialogActions>
                    </form>
                  );
                }}
              </Formik>
            </DialogContent>
          </React.Fragment>
        }
        {isSubmitionCompleted &&
          <React.Fragment>
            <DialogTitle id="form-dialog-title">Thanks!</DialogTitle>
            <DialogContent>
              <DialogContentText>
Thank you              </DialogContentText>
              <DialogActions>
                <Button
                  type="button"
                  className="outline"
                  onClick={handleClose}
                >
                  Close
                  </Button>
                {/* <DisplayFormikState {...props} /> */}
              </DialogActions>
            </DialogContent>
          </React.Fragment>}
      </Dialog>
    </React.Fragment>
  );
}

export default withStyles(styles)(Glossary);

工作示例:

Demo

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

import { Formik, Field } from "formik";
import { Autocomplete } from "formik-material-ui-lab";
import { TextField } from "@material-ui/core";

const options = [
  { title: "The Shawshank Redemption", year: 1994 },
  { title: "Inglourious Basterds", year: 2009 },
  { title: "Snatch", year: 2000 },
  { title: "3 Idiots", year: 2009 },
  { title: "Monty Python and the Holy Grail", year: 1975 }
];

function App() {
  return (
    <Formik
      initialValues={{
        autocomplete: null
      }}
    >
      {() => (
        <Field
          name="autocomplete"
          component={Autocomplete}
          options={options}
          getOptionLabel={(option) => option.title}
          style={{ width: 300 }}
          renderInput={(params) => (
            <TextField {...params} label="Autocomplete" variant="outlined" />
          )}
        />
      )}
    </Formik>
  );
}

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