Formik, Material UI Autocomplete e Firestore - dove interrogare per trovare il parametro dell'array

Sep 09 2020

Come si modifica il gestore onChange di formik in modo che salvi solo il valore (anziché un array del valore più l'etichetta) per le opzioni passate al campo di completamento automatico dell'interfaccia utente materiale?

Ho una collezione che ha un documento con un attributo chiamato categoria. Attualmente, la categoria viene popolata sia con l'etichetta che con il valore dalle opzioni di immissione del modulo.

Sto lottando per trovare un modo per ottenere una base di fuoco in cui interrogare per trovare l'attributo del valore dell'array.

Mi chiedo se potrei avvicinarmi a una soluzione funzionante se provo a salvare solo il valore anziché sia ​​l'etichetta che il valore in firestore.

Ho un modulo Formik con:

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}

Ho provato ciascuno di questi tentativi (uno alla volta) per far sì che l'array si popolasse solo con un singolo campo, ma nessuno di loro funziona per farlo. Firebase registra invece sia l'etichetta che il valore nel suo 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;

Quindi, quando provo a interrogare Firebase, cerco di trovare documenti in cui la categoria include salute.

Ho provato ciascuna delle query where di seguito ma non riesco a ottenere nessuna di esse per restituire i risultati interrogati (posso restituire tutti i risultati se rimuovo la query where):

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

NOTA: funziona per trovare il titolo. Il campo del titolo è allo stesso livello del campo della categoria

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

Ho visto questo post , ma non sono abbastanza intelligente da dare un senso alla risposta.

Ho visto anche questo post e la risposta suggerita da Betty. Ho provato più varianti sul seguente costrutto di query per provare a utilizzare l'idea, ma ogni volta ottengo un errore con il modulo della query.

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

Mi chiedo se posso provare a ottenere il campo del modulo della categoria in formik solo per salvare option.value invece di label e value.

Non riesco a vedere come funziona il formik handleChange per chiedergli di salvare solo il valore.

Anche in questo caso, non riesco a vedere come interrogare Firestore per utilizzare il contenuto di un array come parametro di query.

Qualcuno sa:

  1. come salvare solo il valore dell'opzione (invece dell'etichetta e del valore dell'opzione) nel completamento automatico tramite l'invio del modulo formik a firestore?

  2. come interrogare il contenuto della matrice in firestore per vedere se uno dei suoi attributi corrisponde alla query?

È strano perché questo post suggerisce che una query where su un array dovrebbe essere possibile utilizzando i moduli che ho provato sopra. Tuttavia, questo post suggerisce il seguente formato .collection (study / [docId]). Where ("value", "==", "health"). Ne ho bisogno per cercare in ogni documento nella raccolta, quindi non so come applicare questo approccio a questo problema.

La risposta di gso_gabriel di seguito suggerisce due cose confuse. Innanzitutto, si presume che abbia utilizzato una sottoraccolta. Non l'ho fatto. Aggiungendo l'immagine qui sotto per mostrare il campo della categoria si trova nel documento principale. Posso eseguire una query dove sul titolo utilizzando i formati mostrati sopra per estrarre il valore.

In secondo luogo, e la parte più confusa - dice: "Poiché non puoi cercare un oggetto all'interno di un array". Cosa significa questo? Sta suggerendo che la query non può essere eseguita sul contenuto del valore all'interno del campo categoria? In questo caso, ci sono risorse che forniscono indicazioni su come interrogare questo pezzo di dati?

Ho anche visto questo post - la risposta a cui suggerisce che l'interrogazione del valore all'interno della categoria non è possibile utilizzando Firebase. Il problema è che non riesco a capire l'approccio alternativo suggerito. Se ho capito correttamente questo post, ci sono tutorial che ampliano i principi in modo che io possa provare a trovare una strategia di query diversa?

La prima risposta su questo post suggerisce anche che non è possibile interrogare il valore all'interno della categoria. La seconda risposta suggerisce di utilizzare un formato diverso nella query where, come segue.

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

La risposta sottolinea l'importanza di aggiungere l'intero contenuto dell'array alle parentesi graffe. Funziona.

Quindi, questo mi riporta al gestore di invio del completamento automatico. È un campo a selezione multipla, quindi potrebbe esserci più di un valore selezionato. Come li trasformo in un insieme di valori singoli nel documento Firebase. Anche se ce ne fosse solo uno, come faccio a modificare il gestore di invio in modo che invii solo il valore dell'opzione di selezione anziché sia ​​il valore che l'etichetta?

Se non è possibile interrogare un oggetto in un array, come faccio a modificare il gestore di invio per aggiungere solo i valori delle opzioni selezionate a Firebase, invece di etichette e valori? La soluzione alternativa suggerita nella prima risposta a questo post è aggiungere un campo che contiene solo il valore da interrogare (quindi: salute).

Risposte

3 MohamedRamrami Sep 26 2020 at 14:11

Prima di inviare a Firebase in onSubmitpuoi modificare la forma dei dati che invii

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);
    });
}}
2 gso_gabriel Sep 09 2020 at 19:43

Cercherò di chiarire i tuoi dubbi, rispondendo individualmente ai tuoi punti.

  • Per salvare solo il value, dovrai modificare il modo in cui invii le informazioni a Firestore. Cambia la tua valuesvariabile, poiché anche questa mantiene i valori di label. Per riassumere, dovrai sostanzialmente cambiare il tuo codice quando ti riferisci a Firestore, dove si variabili con labelvalori.
  • Chiarendo questo post che hai menzionato qui , di solito i database NoSQL vengono creati in base al modo in cui eseguirai le query. Quindi, prima correggerai le query come vuoi lavorare con loro e la struttura del tuo database, in modo da non avere problemi con esso. Dato che non puoi cercare un oggetto all'interno di un Array, anzi, direi che ristrutturare il tuo schema sarebbe la scelta migliore.
  • Forse con un sottoraccolta come chiarito in questo esempio qui potrebbe aiutare a rendere le cose più facili.

La modifica sarebbe l'opzione migliore per te, quindi puoi avere uno schema che si adatta esattamente alle tue domande e esigenze: questa è una delle tante parti buone di NoSQL, la libertà di creare lo schema.