【问题标题】:Firebase: How to submit form data to different collections?Firebase:如何将表单数据提交到不同的集合?
【发布时间】:2020-12-04 22:28:37
【问题描述】:

我有一个表格。表单中的字段之一是字段数组 - 用于可重复字段。除此字段外,所有其他表单字段都存储在单个集合(父集合)中。

父集合有一个字段数组的数组,它保存每个重复条目的值,存储在子集合(子集合)中。

当我编写我的 firestore 提交时,我试图将要提交到父集合的字段与要提交到子集合的字段分开。

我的尝试如下。

<Formik
                  initialValues={{ term: "",    category: [],  relatedTerms: [],  }}
                  
                  onSubmit={(values, { setSubmitting }) => {
                     setSubmitting(true);
                     firestore.collection("glossary").doc().set({
                      term: values.term,
                      category: values.category,
                      createdAt: firebase.firestore.FieldValue.serverTimestamp()
                      }),
                      firestore.collection("glossary").doc().collection('relatedTerms').doc().set({
                        dataType: values.dataType,
                        title: values.Title,
                        description: values.description,
                        
                      })
                    .then(() => {
                      setSubmitionCompleted(true);
                    });
                  }}
  

这会产生一个错误提示:

第 120:22 行:应为赋值或函数调用,而不是 看到一个表达式 no-unused-

另外,如何在子集合的提交处理程序中知道父集合的文档引用?

我看到了这个post,它试图在 2 个集合中使用相同的数据(同样关注查找 id)。

我还看到了this blog,它显示了如何在子集合中使用“输入”作为参考,并且似乎有一种方法可以将它们附加到 doc id - 但博客没有显示输入是如何定义。我看不出如何应用该示例。

作为参考,下面列出了带有可重复表单字段数组(在单独的表单中)的主表单。

主窗体

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,
  InputLabel,
  FormControlLabel,
  TextField,
  Typography,
  Box,
  Grid,
  Checkbox,
  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,
} from 'formik-material-ui';

import RelatedTerms from "./Form2";

const allCategories = [
    {value: 'one', label: 'I'},
    {value: 'two', label: 'C'},
    
];


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 Term
          </Button>
        <Dialog
          open={open}
          onClose={handleClose}
          aria-labelledby="form-dialog-title"
        >
          {!isSubmitionCompleted &&
            <React.Fragment>
              <DialogTitle id="form-dialog-title">Create a defined term</DialogTitle>
              <DialogContent>
                <DialogContentText>
                  Your contribution to the research community is appreciated. 
                </DialogContentText>
                <Formik
                  initialValues={{ term: "",  definition: "",  category: [],   context: "", relatedTerms: []  }}
                  
                  onSubmit={(values, { setSubmitting }) => {
                     setSubmitting(true);
                     firestore.collection("glossary").doc().set({
                      term: values.term,
                      definition: values.definition,
                      category: values.category,
                      context: values.context,
                      createdAt: firebase.firestore.FieldValue.serverTimestamp()
                      }),
                      firestore.collection("glossary").doc().collection('relatedTerms').doc().set({
                        dataType: values.dataType,
                        title: values.title,
                        description: values.description,
                        
                      })
                    .then(() => {
                      setSubmitionCompleted(true);
                    });
                  }}
  
                  validationSchema={Yup.object().shape({
                    term: Yup.string()
                      .required('Required'),
                    definition: Yup.string()
                      .required('Required'),
                    category: Yup.string()
                      .required('Required'),
                    context: Yup.string()
                      .required("Required"),
                    // relatedTerms: 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="In what context is this term 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}>
                          <Field
                            name="category"
                            multiple
                            component={Autocomplete}
                            options={allCategories}
                            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>     
                        
                        <FieldArray name="relatedTerms" component={RelatedTerms} />
                        <Button type="submit">Submit</Button>
                        
                        <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>
                  
                </DialogContentText>
                <DialogActions>
                  <Button
                    type="button"
                    className="outline"
                    onClick={handleClose}
                  >
                    Close
                    </Button>
                  {/* <DisplayFormikState {...props} /> */}
                </DialogActions>
              </DialogContent>
            </React.Fragment>}
        </Dialog>
      </React.Fragment>
    );
  }



export default Glossary;

可重复表单字段的字段数组

import React from "react";
import { Formik, Field } from "formik";
import Button from '@material-ui/core/Button';

const initialValues = {
  dataType: "",
  title: "",
  description: "",
  
};

const dataTypes = [
  { value: "primary", label: "Primary (raw) data" },
  { value: "secondary", label: "Secondary data" },
 ];

class DataRequests extends React.Component {
  render() {
    
    const {form: parentForm, ...parentProps} = this.props;

    return (
      <Formik
        initialValues={initialValues}
        render={({ values, setFieldTouched }) => {
          return (
            <div>
              {parentForm.values.relatedTerms.map((_notneeded, index) => {
                return (
                  <div key={index}>
                    
                            <div className="form-group">
                              <label htmlFor="relatedTermsTitle">Title</label>
                              <Field
                                name={`relatedTerms.${index}.title`}
                                placeholder="Add a title"
                                className="form-control"
                                onChange={e => {
                                  parentForm.setFieldValue(
                                    `relatedTerms.${index}.title`,
                                    e.target.value
                                  );
                                }}
                              ></Field>
                            </div>
                          
                            <div className="form-group">
                              <label htmlFor="relatedTermsDescription">
                                Description
                              </label>
                              <Field
                                name={`relatedTerms.${index}.description`}
                                component="textarea"
                                rows="10"
                                placeholder="Describe use"
                                className="form-control"
                                onChange={e => {
                                  parentForm.setFieldValue(
                                    `relatedTerms.${index}.description`,
                                    e.target.value
                                  );
                                }}
                              ></Field>
                            </div>
                          
                            
                            
                          <Button
                            
                            onClick={() => parentProps.remove(index)}
                          >
                            Remove
                          </Button>
                        
                  </div>
                );
              })}
              <Button
                variant="primary"
                size="sm"
                onClick={() => parentProps.push(initialValues)}
              >
                Add another
              </Button>
            </div>
          );
        }}
      />
    );
  }
}

export default DataRequests;

下一次尝试

当我尝试下面 BrettS 提出的建议时,我收到一个控制台警告,上面写着:

警告:从 submitForm() FirebaseError: Function DocumentReference.set() 调用了无效数据,捕获了一个未处理的错误。不支持的字段值:未定义(在字段标题中找到)

我看过 this 帖子,其中讨论了构建要在尝试中使用的对象,但我不知道如何将这些想法应用于这个问题。

我尝试过的另一个尝试如下:

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

                    //   const newGlossaryDocRef = firestore.collection("glossary").doc(); 
                    //   newGlossaryDocRef.set({
                    //     term: values.term,
                    //     definition: values.definition,
                    //     category: values.category,
                    //     context: values.context,
                    //     createdAt: firebase.firestore.FieldValue.serverTimestamp()
                    //     });
                    //   newGlossaryDocRef.collection('relatedTerms').doc().set({
                    // //     dataType: values.dataType,
                    //       title: values.title,
                    // //     description: values.description,
                        
                    //    })

                    const glossaryDoc = firestore.collection('glossary').doc()
                      
                    const relatedTermDoc = firestore
                      .collection('glossary')
                      .doc(glossaryDoc.id) // <- we use the id from docRefA
                      .collection('relatedTerms')
                      .doc()
                      

                    var writeBatch = firestore.batch();

                    writeBatch.set(glossaryDoc, {
                      term: values.term,
                      category: values.category,
                      createdAt: firebase.firestore.FieldValue.serverTimestamp(),
                    });

                    writeBatch.set(relatedTermDoc, {
                      // dataType: values.dataType,
                      title: values.Title,
                      // description: values.description,
                    });

                    writeBatch.commit().then(() => {
                      // All done, everything is in Firestore.
                    })
                    .catch(() => {
                      // Something went wrong.
                      // Using firestore.batch(), we know no data was written if we get here.
                    })
                    .then(() => {
                      setSubmitionCompleted(true);
                    });
                    
                  }}
  

当我尝试这个时,我得到了同样的警告。它说:

警告:从 submitForm() 捕获到未处理的错误 FirebaseError:使用无效数据调用函数 WriteBatch.set()。 不支持的字段值:未定义(在字段标题中找到)

我在使用这种拆分参考格式时遇到另一个错误,它说:

警告:列表中的每个孩子都应该有一个唯一的“key”属性。

我认为这一定与引用的新结构有关 - 但我不知道如何解决它。

下一次尝试

当我尝试 Brett 修改后的建议答案时,我有:

            onSubmit={(values, { setSubmitting }) => {
                 setSubmitting(true);
                 
                //  firestore.collection("glossary").doc().set({
                //   ...values,
                //   createdAt: firebase.firestore.FieldValue.serverTimestamp()
                //   })
                // .then(() => {
                //   setSubmitionCompleted(true);
                // });
              // }}
              const newDocRef = firestore.collection("glossary").doc() 

// auto generated doc id saved here
  let writeBatch = firestore.batch();
  writeBatch.set(newDocRef,{
    term: values.term,
    definition: values.definition,
    category: values.category,
    context: values.context,
    createdAt: firebase.firestore.FieldValue.serverTimestamp()
  });
  writeBatch.set(newDocRef.collection('relatedTerms').doc(),{
    // dataType: values.dataType,
    title: values.title,
    // description: values.description,
  })
  writeBatch.commit()
    .then(() => {
      setSubmitionCompleted(true);
    });
}}

注意,我在relatedTerms 文档上注释了除标题属性之外的所有内容,以便我可以查看这是否有效。

它没有。表单仍然呈现,当我尝试按提交时,它只是挂起。控制台中不会生成任何错误消息,但会生成一条警告消息:

0.chunk.js:141417 警告:从 submitForm() FirebaseError 捕获未处理的错误:函数 WriteBatch.set() 调用 无效数据。不支持的字段值:未定义(在字段中找到 标题)

当我用谷歌搜索这个时 - 从这个 post 看来,可能是在relatedTerm 集合中定义父文档 id 的方式存在问题。

我还想知道是否可能需要为每个集合单独定义和初始化初始值?

当我尝试在控制台记录表单条目的值时,我可以看到一个值为 title 的对象被捕获。表单的初始值包括一个名为relatedTerms 的数组(初始值:[])。

在我尝试将它发送到 Firestore 之前,我可能需要做一些事情来将该数组转换为其中的值。我该怎么做?

我链接的帖子将其分为两个步骤,但我太慢了,无法弄清楚他们在做什么或如何自己做。奇怪的是,当我不尝试在 firestore 集合之间拆分表单值时,不会出现这个问题 - 如果我只使用单个文档,那么这里需要发生的任何事情都是默认完成的。

我不确定我正在尝试做的是否是 Firestore 文档在 custom objects 部分中描述的内容。我注意到上面的添加数据示例显示了添加一个数组,而没有采取任何步骤在提交之前将数组中的项目转换为数据类型。我不确定这是否是正确的查询方式,因为如果我不尝试在集合之间拆分数据,提交工作正常。

下一次尝试

Andreas 在this post 上的回答很简单,我可以理解。展开运算符在相关条款条目的提交方法中使用它的位置起作用。

然而,这引发了下一个挑战——即如何读取子集合数据。 firebase documentation 的这一部分让我感到莫名其妙。我无法理解它。

上面写着:

无法通过移动/网络检索收藏列表 客户端库。

这是否意味着我无法读取relatedTerms 表中的值?

以前,我能够读取相关术语数据数组,如下所示:

function useGlossaryTerms() {
    const [glossaryTerms, setGlossaryTerms] = useState([])
    useEffect(() => {
      firebase
        .firestore()
        .collection("glossary")
        .orderBy('term')
        .onSnapshot(snapshot => {
          const glossaryTerms = snapshot.docs.map(doc => ({
            id: doc.id,
            ...doc.data(),
          }))
          setGlossaryTerms(glossaryTerms)
        })
    }, [])
    return glossaryTerms
  }

然后:

{glossaryTerm.relatedTerms.map(relatedTerm => (
                                
                                <Link to="" className="bodylinks" key={relatedTerm.id}>
                                 {relatedTerm.title}
                          </Link>                                   ))}

relatedTerms 现在是词汇表集合中的子集合,而不是词汇表集合中的数组。我从this post 了解到,我必须单独查询集合。

所以第一个查询是如何获取newDocRef.id 作为属性保存在relatedTerms 文档中。我尝试为它的提交添加一个属性。

glossaryId: newDocRef.id,
    ...values.relatedTerms

虽然当我尝试提交表单时它没有产生任何错误,但它也没有在relatedTerms 文档中创建一个名为glossaryId 的条目。值的日志也不包括它。

我见过this post 和 Jim 的回答。我不明白如何在单独的 useEffect 中使用我的 GlossaryTerm.id 作为文档 ID 来查找相关术语。

【问题讨论】:

  • Firestore(和其他基于文档的数据库)具有实现您想要做的事情的技术,相对而言,更好的方式。如果您不喜欢技术文献,我会从这里开始:youtube.com/watch?v=lW7DWV2jST0
  • 感谢@Cehhiro 的建议。我刚看了这个40分钟的视频。虽然它从理论上讲了如何构建和存储数据,但它实际上并没有回答我的问题。视频中是否有您认为与我提出的问题相关的特定时间戳?
  • 嗨@Mel,所以现在您正在尝试获取子集合,但您无法做到这一点,对吗?抱歉,我没听懂……
  • 嗨@LuisPauloPinto - 是的 - 我在这里发了一个新帖子:stackoverflow.com/questions/63642448/…

标签: javascript firebase google-cloud-firestore formik


【解决方案1】:

每次调用doc() 时,都会生成一个对随机生成的新文档的引用。这意味着您对firestore.collection("glossary").doc() 的第一次调用将生成一个新ID,以及随后的调用。如果您想重用文档引用,则必须将其存储在变量中。

const firstDocRef = firestore.collection("glossary").doc()
firstDocRef.set(...)

稍后使用相同的变量:

const secondDocRef = firstDocRef.collection('relatedTerms').doc()
secondDocRef.set(...)

【讨论】:

  • 但是在提交完成之前不会生成第一个ref?那么 - 如何将词汇表 id 提供给 relatedTerms 集合?
  • 我不确定你在问什么。有什么地方没有按照您的预期工作?
  • 我有一个包含“术语”、“定义”和“相关术语”字段的表单。前两个字段是字符串,并作为文档在词汇表集合中提交,以及相关术语的数组。 relatedTerms 是一个可重复的表单提交(用户可以提交多个字段条目)。每一个都保存在“relatedTerms”子集合中,以及相关词汇表文档的 id。这些术语在一个表单中完成,但提交处理程序需要将数据发送到不同的集合。我正在尝试弄清楚如何提交。
  • 我的建议具体有什么地方没有按照您的预期进行?您是否看到您是如何为立即嵌套在“词汇表”下的文档生成不同的文档 ID 的?
  • 我不明白你的建议。提交时会在词汇表上生成 autoID。我怎样才能抓住它,然后在relatedTerms 字段中使用它?至于你的 consts 的格式,我知道 doc id 放在括号内而不是在:.doc() firstDocRef 之后。我不知道如何在提交处理程序中使用这些常量。
【解决方案2】:

我没有足够的业力或任何东西可以发表评论,所以我将我的评论放在这里。

这是用您的代码实现 Doug 解决方案的一种方法。对于任何语法错误,我深表歉意——我没有测试运行此代码。

即使在提交时生成了 autoID,您也可以在执行前传递文档 ID。

onSubmit={(values, { setSubmitting }) => {
  setSubmitting(true);
  const newDocRef = firestore.collection("glossary").doc() // auto generated doc id saved here
  let writeBatch = firestore.batch();
  writeBatch.set(newDocRef,{
    term: values.term,
    definition: values.definition,
    category: values.category,
    context: values.context,
    createdAt: firebase.firestore.FieldValue.serverTimestamp()
  }),
  writeBatch.set(newDocRef.collection('relatedTerms').doc(),{
    dataType: values.dataType,
    title: values.title,
    description: values.description,
  })
  writeBatch.commit()
    .then(() => {
      setSubmitionCompleted(true);
    });
}}

【讨论】:

  • 我还没有弄清楚如何使用它,当我尝试你给我的东西时,我收到一个错误,上面写着:期望一个赋值或函数调用,而是看到一个表达式 no-未使用的表达式 - 但当我找到一个可行的解决方案时,我会继续尝试并分享我的发现。你让我大开眼界。如果你的方法可以奏效,那么我已经积累了一项新知识。谢谢
  • 我认为问题可能与这个 firebase 警告有关:警告:从 submitForm() FirebaseError 捕获到未处理的错误:使用无效数据调用的函数 DocumentReference.set()。不支持的字段值:未定义(在字段标题中找到)
  • 您确定可以在同一个提交处理程序中使用文档引用吗?
  • 好点。我在这里看到了问题。需要将事情设置为批量写入。将编辑我的答案。
  • Unsupported field value: undefined (found in field title) 错误发生在未定义的情况下。例如尝试firestore.collection('Example').doc().set({ field: undefined }) 会导致此错误。
猜你喜欢
  • 2013-12-15
  • 2015-12-20
  • 1970-01-01
  • 2013-02-16
  • 1970-01-01
  • 2020-06-01
  • 2019-09-18
  • 2011-02-18
  • 1970-01-01
相关资源
最近更新 更多