如何解决Firebase:如何将表单数据提交到不同的集合?
我有一张表格。表单中的字段之一是字段数组-用于可重复的字段。除了此字段外,所有其他表单字段都存储在单个集合(父集合)中。
父集合具有用于字段数组的数组,该数组保存每个重复条目的值,并存储在子集合(子集合)中。
在编写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行:预期分配或函数调用,而不是 看到一个表达式,表示未使用-
此外,如何在Sub Collection的提交处理程序中知道Parent Collection的文档引用?
我已经看到了这个post,它正试图在2个集合中使用相同的数据(出于同样的原因来寻找ID)。
我还看到了this blog,它显示了如何在子集合中使用“输入”作为引用,并且似乎有一种将它们附加到文档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: "",context: "",relatedTerms: [] }}
onSubmit={(values,definition: values.definition,context: values.context,title: values.title,})
.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:函数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,createdAt: firebase.firestore.FieldValue.serverTimestamp(),});
writeBatch.set(relatedTermDoc,{
// dataType: values.dataType,// 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()用无效数据调用。 不支持的字段值:未定义(在字段标题中找到)
此拆分参考格式出现另一个错误,提示:
警告:列表中的每个孩子都应该有一个唯一的“关键”道具。
我认为这一定与引用的新结构有关-但我看不出如何解决它。
下一个尝试
当我尝试布雷特修订后的建议答案时,我会发现:
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,createdAt: firebase.firestore.FieldValue.serverTimestamp()
});
writeBatch.set(newDocRef.collection('relatedTerms').doc(),{
// dataType: values.dataType,})
writeBatch.commit()
.then(() => {
setSubmitionCompleted(true);
});
}}
请注意,我评论了relatedTerms文档中除了title属性之外的所有内容,以便我能看到它是否完全有效。
不是。表单仍然呈现,当我尝试按Submit时,它只是挂起。控制台中不会生成任何错误消息,但是会生成一条警告消息,内容为:
0.chunk.js:141417警告:从submitForm()FirebaseError捕获了未处理的错误:函数WriteBatch.set()调用 无效数据。不支持的字段值:未定义(在字段中找到 标题)
我在Google上搜索时-从此post看来,在relatedTerm集合中定义父级文档ID的方式可能有问题。
我还想知道是否可能需要为每个集合分别定义和初始化初始值?
当我尝试使用控制台记录表单条目的值时,可以看到捕获了一个具有title值的对象。表单的初始值包括一个名为relatedTerms的数组(初始值:[])。
在尝试将其发送到Firestore之前,也许我需要做一些事情以将该数组转换为其中的值。我该怎么办?
我链接的帖子将其分为两个步骤,但我太慢了,无法弄清他们在做什么或自己如何做。奇怪的是,当我不尝试在Firestore集合之间拆分表单值时,不会出现此问题-如果我只使用一个文档,则默认情况下会发生任何事情。
我不确定我要执行的操作是否是custom objects部分中firestore文档所描述的内容。我注意到上面的添加数据示例显示了添加数组,但未采取任何步骤即可在提交之前将数组中的项目转换为数据类型。我不确定这是否是正确的查询,因为如果我不尝试在集合之间拆分数据,则提交工作正常。
下一个尝试
安德烈亚斯(Andreas)对this post的回答很简单,我很容易理解。传播运算符可以在relatedTerms条目的Submit方法中使用的地方使用。
但是,这引发了下一个挑战-即如何读取子集合数据。 firebase documentation的这一部分让我感到困惑。我无法理解。
它说:
无法通过手机/网络检索收藏列表 客户端库。
这是否意味着我无法读取relatedTerms表中的值?
以前,我能够按以下方式读取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文档中创建一个名为lossaryId的条目。值的日志也不包含它。
我看过this post和吉姆的回答。我不明白如何在单独的useEffect中使用我的glossaryTerm.id作为文档ID来查找相关条款。
解决方法
每次调用doc()
时,都将生成对新随机生成的文档的引用。这意味着您对firestore.collection("glossary").doc()
的第一次调用将生成一个新的ID,以及随后的调用。如果要重用文档引用,则必须将其存储在变量中。
const firstDocRef = firestore.collection("glossary").doc()
firstDocRef.set(...)
稍后使用相同的变量:
const secondDocRef = firstDocRef.collection('relatedTerms').doc()
secondDocRef.set(...)
,
我没有足够的业力或其他东西要评论,所以我在这里发表评论。
这是用您的代码实现Doug解决方案的一种方法。抱歉,对于任何语法错误-我没有测试运行此代码。
即使在提交时生成了自动ID,您也可以在执行前传递文档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);
});
}}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。