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

时间:2020-08-08 07:42:24

标签: javascript reactjs material-ui formik formik-material-ui

我正在尝试找出如何遵循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也带有下划线。我不知道为什么。

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

我有一个带有两个“自动完成”字段的表单。目前看起来像这样。

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

Material-UI:返回了自动完成的getOptionLabel方法 未定义,而不是“”的字符串。

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

任何人都可以看到如何与上面链接中发布的文档一致地使用Formik,材料ui进行自动完成吗?

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

<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警告:组件正在更改的不受控制的输入 类型undefined要控制。输入元素不应从 不受控制到受控制(反之亦然)。确定使用 受控或非受控输入元件的使用寿命 零件。更多信息

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

在这一点上-我不在乎自动填充或选择使用哪个,我只想让其中一个工作。

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

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

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

我还尝试将code sandbox中的性别选择表单字段添加到表单中,以查看是否可以使用此功能。当我注释掉上述类别和共享字段,并添加默认值为空字符串的性别字段时,将加载表单。

字段为:

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

出现性别选择字段,但宽度约为1厘米,并且选项菜单中没有选项,我什么也无法选择。但是表单确实会加载到firebase中,性别字段中使用空字符串。那是进步,但还不足以推进。

同一代码沙箱显示了一个使用“自动完成”的字段。我尝试对其进行调整,并以如下形式在其形式中使用它:

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

尝试此操作时,出现错误消息:

TypeError:renderInput不是函数

此错误消息对我来说毫无意义,因为我没有在表单的任何地方使用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>

我没有任何错误,可以保存带有选项详细信息的表单。但是,这实际上并不能解决有关“自动完成”为何不起作用的问题。这也没有使用链接文档中所示的“选择”字段。因此,我不清楚这为什么起作用,或者为什么文档中显示的方法不起作用。

下一个尝试

使用此codesandbox中的自动填充示例作为指南,我尝试过:

<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,如代码沙箱所示。

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

警告:自动完成的值不是数组,这可能导致 意外的行为

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

TypeError:无法读取未定义的属性“ toLowerCase”

Material-UI:返回了自动完成的getOptionLabel方法 未定义,而不是{“ value”:“ open”,“ label”:“ Open “}。

2 个答案:

答案 0 :(得分:3)

工作示例:

this

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

答案 1 :(得分:1)

对于其他被卡住的人,这是可行的-尽管我不明白为什么。上面文章中指出的带下划线的错误仍然存​​在,我不知道如何解决。

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

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