Formik,Material UI 自动完成和 Firestore - 在哪里查询查找数组参数

Formik, Material UI Autocomplete and Firestore - where query to find the array parameter

如何修改 formik onChange 处理程序,以便它只保存传递给 Material UI 自动完成字段的选项的值(而不是值加上标签的数组)?

我有一个 collection,它有一个文档,该文档的属性称为类别。目前,该类别由表单输入选项中的标签和值填充。

我正在努力寻找一种方法来获取 firebase where 查询以查找数组的值属性。

我想知道如果我尝试只将值而不是标签和值都保存到 firestore 中,我是否可以更接近一个可行的解决方案。

我有一个 Formik 表格:

import React, { useState } from "react";
import ReactDOM from "react-dom";
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,
  Divider,
  InputLabel,
  FormControlLabel,
  TextField,
  Typography,
  Box,
  Grid,
  Dialog,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
} from '@material-ui/core';
import MuiTextField from '@material-ui/core/TextField';


import {
  Formik, Form, Field, ErrorMessage, FieldArray,
} from 'formik';


import * as Yup from 'yup';
import {
  Autocomplete,
  ToggleButtonGroup,
  AutocompleteRenderInputParams,
} from 'formik-material-ui-lab';
import {
  fieldToTextField,
  TextFieldProps,
  Select,
  Switch,
  CheckboxWithLabel,
  Checkbox
} from 'formik-material-ui';


const allCategories = [
    {value: 'health', label: 'Health & Medical'},
    {value: 'general', label: 'General'},    
];


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 Summary(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 
          </Button>
        <Dialog
          open={open}
          onClose={handleClose}
          aria-labelledby="form-dialog-title"
        >
          {!isSubmitionCompleted &&
            <React.Fragment>
              
              <DialogContent>
                <Formik
                  initialValues={{ title: "",  category: [], subcategory: "" }}
                  
                  onSubmit={(values, { setSubmitting }) => {
                     setSubmitting(true);
                     
                 
    firestore.collection("study").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'),
                    
                  })}
                >
                  {(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"
                            multiple
                            component={Autocomplete}
                            options={allCategories}
                            // value={values.label}
                            // value={values.value}
                            // value={allCategories.value} 
                           // value={values.category.allCategories.value}

I tried each of these attempts (one at a time) at getting the array to populate with a single field only - but none of them work to do that. Instead, firebase records both label and value in its array.

                            getOptionLabel={(option: any) => option.label}
                            style={{width: '100%'}}
                            renderInput={(params: AutocompleteRenderInputParams) => (
                              <MuiTextField
                                {...params}
                                error={touched['autocomplete'] && !!errors['autocomplete']}
                                helperText={touched['autocomplete'] && errors['autocomplete']}
                                label="Category"
                                variant="outlined"
                              />
                            )}
                          />
                        </Box> 
                        
                        
                        <TextField
                          label="Subcategory "
                          name="subcategory"
                          //   className={classes.textField}
                          value={values.subcategory}
                          onChange={handleChange}
                          onBlur={handleBlur}
                          helperText={(errors.subcategory && touched.subcategory) && errors.subcategory}
                          margin="normal"
                          style={{ width: "100%"}}
                        />
  
                        
                      
                        <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">Done!</DialogTitle>
              <DialogContent>
               
                <DialogActions>
                  <Button
                    type="button"
                    className="outline"
                    onClick={handleClose}
                  >
                    Close
                    </Button>
                  {/* <DisplayFormikState {...props} /> */}
                </DialogActions>
              </DialogContent>
            </React.Fragment>}
        </Dialog>
      </React.Fragment>
    );
  }

export default Summary;

然后当我尝试查询 firebase 时,我试图找到类别包含健康的文档。

我已经尝试了下面的每个 where 查询,但我无法将它们中的任何一个 return 查询结果(如果我删除 where 查询,我可以 return 所有结果) :

function useHealthTerms() {
    const [healthTerms, setHealthTerms] = useState([])
    useEffect(() => {
      firebase
        .firestore()
        .collection("study")
    //.where("title", "==", "ss") 

NOTE - this works to find the title. The title field is at the same level as the category field

        //.where('category', '==', 'health')
        //.where('category.value', "array-contains", 'health")
        //.where('category', 'array-contains', 'health')
        //.where('category', 'array-contains', 1)
    //.where("category.1.value", '==', 'health')
        .onSnapshot(snapshot => {
          const healthTerms = snapshot.docs.map(doc => ({
            id: doc.id,
            ...doc.data(),
          }))
          setHealthTerms(healthTerms)
        })
    }, [])
    return healthTerms
  }

我看过,但我不够聪明,无法从答案中理解任何意义。

我也看到了 和贝蒂建议的答案。我尝试了以下查询结构的多种变体来尝试使用这个想法,但每次,我都会收到查询形式的错误。

.where(new firebase.firestore().FieldPath("category", "value"), '==', 'health')

我想知道是否可以尝试在 formik 中获取类别表单字段以保存 option.value 而不是标签和值。

我看不出 formik handleChange 是如何要求它只保存值的。

即便如此,我也看不出如何查询 firestore 以使用数组的内容作为查询参数。

有谁知道:

  1. 如何通过 formik 表单提交到 firestore 仅保存自动完成中的选项值(而不是选项标签和值)?

  2. 如何在firestore中查询数组的内容,看它的某个属性是否匹配查询?

这很奇怪,因为 建议可以使用我在上面尝试过的形式对数组进行 where 查询。但是,此 post 建议采用以下格式 .collection(study/[docId]).where("value", "==", "health")。我需要它来搜索 collection 中的每个文档,所以我不知道如何将这种方法应用于此问题。

下面 gso_gabriel 的回答暗示了两点令人困惑的事情。首先,假设我使用了一个subcollection。我没有。添加下图以显示类别字段位于 parent 文档中。我可以使用上面显示的格式对标题进行 where 查询以提取值。

其次,最令人困惑的一点是:“因为您无法在数组中搜索 object”。这是什么意思?是不是提示category字段里面value的内容无法查询?如果是这种情况,是否有资源提供有关如何查询此数据的指导?

我也看到了 - 答案表明无法使用 firebase 查询类别内的值。问题是我无法理解建议的替代方法。如果我对这个 post 的理解正确,是否有任何扩展原理的教程,以便我可以尝试找到不同的查询策略?

上的第一个答案还表明无法查询类别内的值。第二个答案建议在 where 查询中使用不同的格式 - 如下所示。

.where("category", "array-contains", {value: "health", label: "Health & Medical"})

答案强调了将整个数组内容添加到花括号中的重要性。这行得通。

所以 - 这让我回到了自动完成提交处理程序。这是一个多 select 字段,因此可能有多个 select 值。我如何将它们变成 firebase 文档中的一组单一值。即使只有一个,我如何更改提交处理程序以便它只发送 select 选项值而不是值和标签?

如果无法在数组中查询 object - 我该如何更改提交处理程序以仅将 selected 选项值添加到 firebase,而不是标签和值? 的第一个答案中建议的解决方法是添加一个仅包含要查询的值的字段(因此:health)。

我会通过逐个回答您的观点来澄清您的疑惑。

  • 为了仅保存 value,您需要更改向 Firestore 发送信息的方式。更改您的 values 变量,因为这个变量也维护 label 中的值。总而言之,在与 Firestore 相关时,您需要基本上更改您的代码,其中您的变量具有 label 个值。
  • 澄清这一点 post 您提到 - NoSQL 数据库通常是根据您将要查询的方式创建的。因此,您将首先根据要使用的查询和数据库的结构来调整查询,这样您就不会遇到问题。由于您无法在 Array 中搜索对象,因此我认为重组您的方案将是最佳选择。
  • 也许使用本示例中阐明的子集合 可能会帮助您简化事情。

更改将是您的最佳选择,因此您可以拥有一个完全适合您的查询和需求的方案 - 这是 NoSQL 的众多优点之一,即创建方案的自由。

onSubmit 中提交到 firebase 之前,您可以更改发送的数据的形状

onSubmit={(values, { setSubmitting }) => {
    setSubmitting(true);

    firestore.collection("study").doc().set({
     ...values,
     category: values.category.map(c => c.value),
     createdAt: firebase.firestore.FieldValue.serverTimestamp()
    })
    .then(() => {
        setSubmitionCompleted(true);
    });
}}