Firebase: Wie kann ich Formulardaten an verschiedene Sammlungen senden?

Aug 15 2020

Ich habe eine Form. Eines der Felder im Formular ist ein Feldarray - für wiederholbare Felder. Abgesehen von diesem Feld werden alle anderen Formularfelder in einer einzigen Sammlung (der übergeordneten Sammlung) gespeichert.

Die übergeordnete Sammlung verfügt über ein Array für das Feldarray, das die Werte jedes wiederholten Eintrags enthält und in einer Untersammlung (der Untersammlung) gespeichert werden soll.

Wenn ich meine Firestore-Übermittlung schreibe, versuche ich, die Felder, die an die übergeordnete Sammlung übermittelt werden sollen, von den Feldern zu trennen, die an die untergeordnete Sammlung übermittelt werden sollen.

Mein Versuch ist unten.

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

Dies erzeugt einen Fehler, der besagt:

Zeile 120: 22: Erwartete eine Zuweisung oder einen Funktionsaufruf und sah stattdessen einen Ausdruck, der nicht verwendet wurde.

Wie kann ich außerdem die Dokumentreferenz der übergeordneten Sammlung im Submit-Handler für die Untersammlung bekannt machen?

Ich habe diesen Beitrag gesehen , in dem versucht wird, dieselben Daten in zwei Sammlungen zu verwenden (mit der gleichen Sorge, die ID zu finden).

Ich habe auch diesen Blog gesehen, der zeigt, wie "Eingaben" als Referenz in einer Untersammlung verwendet werden, und anscheinend eine Möglichkeit hat, sie an eine Dokument-ID anzuhängen - aber der Blog zeigt nicht, wie Eingaben definiert sind. Ich kann nicht sehen, wie ich dieses Beispiel anwenden soll.

Als Referenz wird das Hauptformular mit dem Feldarray für wiederholbare Formulare (in einem separaten Formular) unten aufgeführt.

Hauptform

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;

Feldarray für wiederholbares Formularfeld

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;

NÄCHSTER VERSUCH

Wenn ich den von BrettS unten dargelegten Vorschlag versuche, erhalte ich eine Konsolenwarnung mit der Aufschrift:

Warnung: Ein nicht behandelter Fehler wurde von submitForm () abgefangen. FirebaseError: Funktion DocumentReference.set () wurde mit ungültigen Daten aufgerufen. Nicht unterstützter Feldwert: undefiniert (im Feldtitel gefunden)

Ich habe diesen Beitrag gesehen, in dem es um die Strukturierung des Objekts geht, das für den Versuch verwendet werden soll, aber ich kann nicht sehen, wie diese Ideen auf dieses Problem angewendet werden können.

Ein weiterer Versuch, den ich versucht habe, ist unten aufgeführt:

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

Wenn ich das versuche, bekomme ich die gleiche Warnung. Es sagt:

Warnung: Ein nicht behandelter Fehler wurde von submitForm () abgefangen. FirebaseError: Funktion WriteBatch.set () wurde mit ungültigen Daten aufgerufen. Nicht unterstützter Feldwert: undefiniert (im Feldtitel gefunden)

Ich erhalte einen weiteren Fehler mit diesem geteilten Referenzformat, der besagt:

Warnung: Jedes Kind in einer Liste sollte eine eindeutige "Schlüssel" -Stütze haben.

Ich denke, das muss etwas mit der neuen Struktur der Referenzen zu tun haben - aber ich kann nicht sehen, wie ich das angehen soll.

NÄCHSTER VERSUCH

Wenn ich Bretts überarbeitete vorgeschlagene Antwort versuche, habe ich:

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

Beachten Sie, dass ich alles außer dem title-Attribut im relatedTerms-Dokument kommentiert habe, damit ich sehen kann, ob dies überhaupt funktioniert.

Das tut es nicht. Das Formular wird immer noch gerendert und wenn ich versuche, auf "Senden" zu klicken, hängt es einfach. In der Konsole werden keine Fehlermeldungen generiert, es wird jedoch eine Warnmeldung generiert, die besagt:

0.chunk.js: 141417 Warnung: Ein nicht behandelter Fehler wurde von submitForm () abgefangen. FirebaseError: Funktion WriteBatch.set () wurde mit ungültigen Daten aufgerufen. Nicht unterstützter Feldwert: undefiniert (im Feldtitel gefunden)

Wenn ich dies google, geht aus diesem Beitrag hervor, dass möglicherweise ein Problem mit der Definition der Dokument-ID des übergeordneten Elements in der relatedTerm-Sammlung vorliegt.

Ich frage mich auch, ob die Anfangswerte möglicherweise für jede Sammlung separat definiert und initialisiert werden müssen.

Wenn ich versuche, die Werte der Formulareinträge in der Konsole zu protokollieren, kann ich sehen, dass ein Objekt mit dem Wert title erfasst wird. Die Anfangswerte für das Formular enthalten ein Array namens relatedTerms (Anfangswert: []).

Vielleicht muss ich etwas tun, um dieses Array in die darin enthaltenen Werte zu konvertieren, bevor ich versuche, dies an den Firestore zu senden. Wie würde ich das machen?

Der Beitrag, den ich verlinkt habe, unterteilt dies in zwei Schritte, aber ich bin zu langsam, um herauszufinden, was sie tun oder wie sie selbst gemacht werden. Es ist jedoch seltsam, dass dieses Problem nicht auftritt, wenn ich nicht versuche, die Formularwerte auf Firestore-Sammlungen aufzuteilen. Wenn ich nur ein einzelnes Dokument verwende, wird standardmäßig alles getan, was hier geschehen muss.

Ich bin nicht sicher, ob ich versuche, das zu tun, was die Firestore-Dokumente im Abschnitt " Benutzerdefinierte Objekte " beschreiben. Ich stelle fest, dass das obige Beispiel zum Hinzufügen von Daten das Hinzufügen eines Arrays zeigt, ohne dass Schritte unternommen wurden, um die Elemente im Array vor dem Senden in den Datentyp zu konvertieren. Ich bin mir nicht sicher, ob dies die richtige Untersuchungslinie ist, da die Übermittlung einwandfrei funktioniert, wenn ich nicht versuche, die Daten zwischen Sammlungen aufzuteilen.

NÄCHSTER VERSUCH

Die Antwort von Andreas auf diesen Beitrag ist so einfach, dass ich sie verstehen kann. Der Spread-Operator arbeitet dort, wo er in der Submit-Methode für die relatedTerms-Einträge verwendet wird.

Dies wirft jedoch die nächste Herausforderung auf - das Lesen der Untersammlungsdaten. Dieser Teil der Firebase-Dokumentation verwirrt mich. Ich kann es nicht verstehen.

Es sagt:

Das Abrufen einer Liste von Sammlungen ist mit den Mobil- / Webclient-Bibliotheken nicht möglich.

Bedeutet das, dass ich die Werte in der relatedTerms-Tabelle nicht lesen kann?

Zuvor konnte ich das Array von relatedTerms-Daten wie folgt lesen:

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
  }

dann:

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

relatedTerms ist jetzt eine Untersammlung in der Glossarsammlung anstelle eines Arrays in der Glossarsammlung. Ich verstehe aus diesem Beitrag, dass ich die Sammlungen separat abfragen muss.

Die erste Abfrage lautet also, wie newDocRef.id als Attribut im relatedTerms-Dokument gespeichert werden kann. Ich habe versucht, dem Submit ein Attribut hinzuzufügen.

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

Beim Versuch, das Formular zu senden, wurden zwar keine Fehler generiert, es wurde jedoch auch kein Eintrag im Dokument relatedTerms mit dem Namen glossaryId erstellt. Das Werteprotokoll enthält es auch nicht.

Ich habe diesen Beitrag und die Antwort von Jim gesehen. Ich verstehe nicht, wie ich meine glossaryTerm.id als Dokument-ID in einem separaten useEffect verwenden soll, um die zugehörigen Begriffe zu finden.

Antworten

6 DougStevenson Aug 15 2020 at 12:24

Jedes Mal doc(), wenn Sie anrufen , generieren Sie einen Verweis auf ein neues zufällig generiertes Dokument. Das bedeutet, dass Ihr erster Anruf bei firestore.collection("glossary").doc()eine neue ID sowie den nachfolgenden Anruf generiert. Wenn Sie eine Dokumentreferenz wiederverwenden möchten, müssen Sie sie in einer Variablen speichern.

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

Die gleiche Variable später verwenden:

const secondDocRef = firstDocRef.collection('relatedTerms').doc()
secondDocRef.set(...)
3 BrettS Aug 24 2020 at 08:34

Ich habe nicht genug Karma oder was auch immer, um es zu kommentieren, also schreibe ich meinen Kommentar hier.

Hier ist eine Möglichkeit, die Lösung von Doug mit Ihrem Code zu implementieren. Entschuldigung im Voraus für Syntaxfehler - Ich habe diesen Code nicht getestet.

Sie können Dokument-IDs vor der Ausführung übergeben, obwohl die autoID beim Senden generiert wird.

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