How do you modify the formik onChange handler so that it saves the value only (rather than an array of the value plus label) for options passed to Material UI Autocomplete field?
I have a collection which has a document with an attribute called category. Currently, the category gets populated with both the label and value from the form entry options.
I'm struggling to find a way to get a firebase where query to find the value attribute of the array.
I'm wondering if I might get closer to a working solution if I try to just save the value instead of both the label and the value into firestore.
I have a Formik form with:
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;
Then when I try to query firebase, I'm trying to find documents where the category includes health.
I have tried each of the where queries below but I can't get any of them to return the queried results (I can return all the results if I remove the where query):
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
}
I have seen this post, but I'm not clever enough to make any sense from the answer to it.
I've also seen this post and the answer suggested by Betty. I've tried multiple variations on the following query construct to try and use the idea, but each time, I get an error with the form of the query.
.where(new firebase.firestore().FieldPath("category", "value"), '==', 'health')
I'm wondering if I can try to get the category form field in formik just to save the option.value instead of both label and value.
I can't see how the formik handleChange works to ask it to save just the value.
Even then, I can't see how to query firestore to use the content of an array as a query parameter.
Does anyone know:
how to save just the option value (instead of both option label and value) in the Autocomplete via formik form submission to firestore?
how to query the content of the array in firestore to see if one of its attributes matches the query?
It's strange because this post suggests that a where query on an array should be possible using the forms I've tried above. However, this post suggests the following format .collection(study/[docId]).where("value", "==", "health"). I need it to search each document in the collection so I don't know how to apply that approach to this problem.
The answer from gso_gabriel below suggests two confusing things. First, there is an assumption that I have used a subcollection. I haven't. Adding the picture below to show the category field is in the parent document. I can do a where query on title using the formats shown above to extract the value.
Secondly, and the bit that is most confusing - it says: "As you can't search for a object inside an Array". What does this mean? Is it suggesting that the query cannot be performed on the content of value inside the category field? If this is the case, are there resources providing guidance for how to query this piece of data?
I have also seen this post - the answer to which suggests that querying value within category is not possible using firebase. The problem is that I can't understand the suggested alternative approach. If I have understood this post correctly, are there any tutorials which expand on the principles so that I can try to find a different query strategy?
The first answer on this post also suggests that it isn't possible to query value inside category. The second answer suggests using a different format in the where query - as follows.
.where("category", "array-contains", {value: "health", label: "Health & Medical"})
The answer stresses the importance of adding the entire array contents to the curly braces. This works.
So - this brings me back to the Autocomplete submit handler. It's a multiple select field, so there may be more than one value selected. How do I make those into a set of single values on the firebase document. Even if there were only one, how do I change the submit handler so that it only sends the select option value instead of both the value and the label?
If it isn't possible to query a object in an array - how do I change the submit handler to add just the selected option(s) values to firebase, instead of both labels and values? The suggested workaround in the first answer to this post is to add a field which just holds the value to be queried (so: health).