开发者问题收集

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

2020-08-08
8826

我正在尝试弄清楚如何按照 此处 中 Formik、Material UI、React 工具的 Autocomplete 字段文档中的说明进行操作。

文档中给出的示例是:

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

没有给出 Movie 在 getOptionLabel 中的含义的线索。当我尝试使用它时,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);

有人能看到如何让自动完成功能与 formik、material 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 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

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

此时 - 我不在乎使用自动完成还是选择,我只想让其中一个工作。

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

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

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

我还尝试将此 代码沙箱 中的性别选择表单字段添加到我的表单中,看看我是否可以让它工作。当我注释掉上面描述的类别和共享字段,并添加默认值为空字符串的性别字段时,表单会加载。

字段为:

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

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

下次尝试

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

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

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 "}.

2个回答

工作示例:

演示

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);
Kuf
2020-08-15

对于其他遇到困难的人来说,这种方法很管用 - 虽然我不明白为什么。上面帖子中指出的下划线错误仍然存​​在,我不知道如何解决它们。

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

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);
Mel
2020-08-13