Form validation with Yup (Checkbox) doesn't work - javascript

I've got a from build with mui, formik and yup.
If the input fields are empty (f.ex. "surname") after clicking on the submit-button an error shows up. The problem is, that an error doesn't show up, if the checkbox isn't checked (Terms of Service).
Here's the relevant code:
const formSchema = yup.object().shape({
surname: yup.string().required("Surname is missing"),
termsOfService: yup
.bool()
.oneOf([true], "Please accept")
.required("Please accept"),
});
export default form() {
const formik = useFormik({
initialValues: {
surname: "",
termsOfService: false,
},
validationSchema: formSchema,
onSubmit: () => {
//logic
},
});
return (
<form onSubmit={formik.handleSubmit}>
<TextField
focused
color="secondary"
sx={{
m: 0.5,
marginBottom: 3,
}}
id="surname"
name="surname"
label="Vollständiger Vorname"
value={formik.values.surname}
onChange={formik.handleChange}
error={formik.touched.surname && Boolean(formik.errors.surname)}
/>
<div>
<Checkbox
{...label}
color="secondary"
sx={{ color: "#9E3FB0", marginLeft: "-6px" }}
id="termsOfService"
name="termsOfService"
label="termsOfService"
value={formik.values.termsOfService}
onChange={formik.handleChange}
error={
formik.touched.termsOfService &&
Boolean(formik.errors.termsOfService)
}
helperText={
formik.touched.termsOfService &&
Boolean(formik.errors.termsOfService)
}
/>
<p className={styles.pNoteAGB}>Accept.</p>
</div>
<ButtonDefault text="Analyse starten" type="submit" />
</form>
)
}
Any ideas what's wrong?

Checkbox doesn't have error field of its own like TextField. You need to use FormControl and FormHelperText to achieve that. I have added updated code below. and also added FormControlLabel. Let me know if you require further changes in it.
import React, { useState, useEffect } from "react";
import * as yup from "yup";
import { useFormik } from "formik";
import {
TextField,
Checkbox,
Button,
Alert,
FormHelperText,
FormControl,
FormControlLabel
} from "#mui/material";
import "./styles.css";
const formSchema = yup.object().shape({
surname: yup.string().required("Missing surname!"),
termsOfService: yup
.bool()
.oneOf([true], "Please accept.")
.required("Please accept.")
});
export default function App() {
const [alert, setAlert] = useState("");
const formik = useFormik({
initialValues: {
surname: "",
termsOfService: false
},
validationSchema: formSchema,
validateOnChange:false,
onSubmit: (values) => {
setAlert("success");
}
});
useEffect(() => {}, [alert]);
return (
<div className="App">
{alert === "success" && <Alert severity="success">Success!</Alert>}
<form onSubmit={formik.handleSubmit}>
<div>
<TextField
focused
color="secondary"
sx={{
m: 0.5,
marginBottom: 3
}}
id="surname"
name="surname"
label="Surname"
value={formik.values.surname}
onChange={formik.handleChange}
error={!!formik.errors.surname}
helperText={formik.errors.surname ?? " "}
/>
</div>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
<FormControl error={!!formik.errors.termsOfService}>
<FormControlLabel
label="Accept."
id="termsOfService"
control={
<Checkbox
color="secondary"
sx={{
color: `${
!!formik.errors.termsOfService ? "error.main" : "#9E3FB0"
}`,
marginLeft: "-6px"
}}
name="termsOfService"
value={formik.values.termsOfService}
onChange={formik.handleChange}
/>
}
/>
<FormHelperText>
{formik.errors.termsOfService ?? " "}
</FormHelperText>
</FormControl>
</div>
<Button variant="contained" type="submit">
Start Analysis
</Button>
</form>
</div>
);
}

Related

A component is changing an uncontrolled input to be controlled in React.js

Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
I am creating a Social Media App and facing this problems, what is wrong in my code?
Following is my code:
import { useState } from "react";
import {
Box,
Button,
TextField,
useMediaQuery,
Typography,
useTheme,
} from "#mui/material";
import EditOutlinedIcon from "#mui/icons-material/EditOutlined";
import { Formik } from "formik";
import * as yup from "yup";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { setLogin } from "state";
import Dropzone from "react-dropzone";
import FlexBetween from "components/FlexBetween";
const registerSchema = yup.object().shape({
firstName: yup.string().required("required"),
lastName: yup.string().required("required"),
email: yup.string().email("invalid email").required("required"),
password: yup.string().required("required"),
location: yup.string().required("required"),
occupation: yup.string().required("required"),
picture: yup.string().required("required"),
});
const loginSchema = yup.object().shape({
email: yup.string().email("invalid email").required("required"),
password: yup.string().required("required"),
});
const initialValuesRegister = {
firstName: "",
lastName: "",
email: "",
password: "",
location: "",
occupation: "",
picture: "",
};
const initialValuesLogin = {
email: "",
password: "",
};
const Form = () => {
const [pageType, setPageType] = useState("login");
const { palette } = useTheme();
const dispatch = useDispatch();
const navigate = useNavigate();
const isNonMobile = useMediaQuery("(min-width:600px)");
const isLogin = pageType === "login";
const isRegister = pageType === "register";
const register = async (values, onSubmitProps) => {
// this allows us to send form info with image
const formData = new FormData();
for (let value in values) {
formData.append(value, values[value]);
}
formData.append("picturePath", values.picture.name);
const savedUserResponse = await fetch(
"http://localhost:3001/auth/register",
{
method: "POST",
body: formData,
}
);
const savedUser = await savedUserResponse.json();
onSubmitProps.resetForm();
if (savedUser) {
setPageType("login");
}
};
const login = async (values, onSubmitProps) => {
const loggedInResponse = await fetch("http://localhost:3001/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(values),
});
const loggedIn = await loggedInResponse.json();
onSubmitProps.resetForm();
if (loggedIn) {
dispatch(
setLogin({
user: loggedIn.user,
token: loggedIn.token,
})
);
navigate("/home");
}
};
const handleFormSubmit = async (values, onSubmitProps) => {
if (isLogin) await login(values, onSubmitProps);
if (isRegister) await register(values, onSubmitProps);
};
return (
<Formik
onSubmit={handleFormSubmit}
initialValues={isLogin ? initialValuesLogin : initialValuesRegister}
validationSchema={isLogin ? loginSchema : registerSchema}
>
{({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
setFieldValue,
resetForm,
}) => (
<form onSubmit={handleSubmit}>
<Box
display="grid"
gap="30px"
gridTemplateColumns="repeat(4, minmax(0, 1fr))"
sx={{
"& > div": { gridColumn: isNonMobile ? undefined : "span 4" },
}}
>
{isRegister && (
<>
<TextField
label="First Name"
onBlur={handleBlur}
onChange={handleChange}
value={values.firstName}
name="firstName"
error={
Boolean(touched.firstName) && Boolean(errors.firstName)
}
helperText={touched.firstName && errors.firstName}
sx={{ gridColumn: "span 2" }}
/>
<TextField
label="Last Name"
onBlur={handleBlur}
onChange={handleChange}
value={values.lastName}
name="lastName"
error={Boolean(touched.lastName) && Boolean(errors.lastName)}
helperText={touched.lastName && errors.lastName}
sx={{ gridColumn: "span 2" }}
/>
<TextField
label="Location"
onBlur={handleBlur}
onChange={handleChange}
value={values.location}
name="location"
error={Boolean(touched.location) && Boolean(errors.location)}
helperText={touched.location && errors.location}
sx={{ gridColumn: "span 4" }}
/>
<TextField
label="Occupation"
onBlur={handleBlur}
onChange={handleChange}
value={values.occupation}
name="occupation"
error={
Boolean(touched.occupation) && Boolean(errors.occupation)
}
helperText={touched.occupation && errors.occupation}
sx={{ gridColumn: "span 4" }}
/>
<Box
gridColumn="span 4"
border={`1px solid ${palette.neutral.medium}`}
borderRadius="5px"
p="1rem"
>
<Dropzone
acceptedFiles=".jpg,.jpeg,.png"
multiple={false}
onDrop={(acceptedFiles) =>
setFieldValue("picture", acceptedFiles[0])
}
>
{({ getRootProps, getInputProps }) => (
<Box
{...getRootProps()}
border={`2px dashed ${palette.primary.main}`}
p="1rem"
sx={{ "&:hover": { cursor: "pointer" } }}
>
<input {...getInputProps()} />
{!values.picture ? (
<p>Add Picture Here</p>
) : (
<FlexBetween>
<Typography>{values.picture.name}</Typography>
<EditOutlinedIcon />
</FlexBetween>
)}
</Box>
)}
</Dropzone>
</Box>
</>
)}
<TextField
label="Email"
onBlur={handleBlur}
onChange={handleChange}
value={values.email}
name="email"
error={Boolean(touched.email) && Boolean(errors.email)}
helperText={touched.email && errors.email}
sx={{ gridColumn: "span 4" }}
/>
<TextField
label="Password"
type="password"
onBlur={handleBlur}
onChange={handleChange}
value={values.password}
name="password"
error={Boolean(touched.password) && Boolean(errors.password)}
helperText={touched.password && errors.password}
sx={{ gridColumn: "span 4" }}
/>
</Box>
{/* BUTTONS */}
<Box>
<Button
fullWidth
type="submit"
sx={{
m: "2rem 0",
p: "1rem",
backgroundColor: palette.primary.main,
color: palette.background.alt,
"&:hover": { color: palette.primary.main },
}}
>
{isLogin ? "LOGIN" : "REGISTER"}
</Button>
<Typography
onClick={() => {
setPageType(isLogin ? "register" : "login");
resetForm();
}}
sx={{
textDecoration: "underline",
color: palette.primary.main,
"&:hover": {
cursor: "pointer",
color: palette.primary.light,
},
}}
>
{isLogin
? "Don't have an account? Sign Up here."
: "Already have an account? Login here."}
</Typography>
</Box>
</form>
)}
</Formik>
);
};
export default Form;
The file upload input is changing from undefined to defined (thus changing it from uncontrolled to controlled which is not good practice in react) since it has no initial value. I haven't used formik alot but you should read through the docs and check how to set initial value for a dropzone.

Validation is not working with useState hook

I am trying to validate my form so that it does not send if there is no name, or email input. I am using emailjs for the form functionality and everything is working. I've used this code before for validating forms when using material UI. Not sure why it's not working.
const form = useRef();
// Validations
const [name, setName] = useState("");
const [error, setError] = useState("");
const handleError = (e) => {
setName(e.target.value);
if (e.target.value.length < 1) {
setError("Enter name");
}
}
// send email
const sendEmail = (e) => {
e.preventDefault();
emailjs.sendForm('sID', 'tID', form.current, 'XXXX')
.then((result) => {
console.log(result.text);
}, (error) => {
console.log(error.text);
});
e.target.reset()
};
<form ref={form} onSubmit={sendEmail}>
<Grid container>
{error ?
<p style={{ color: '#D35E3C' }}>{error}</p> : ''}
<Grid item>
<TextField onChange={handleError} id="standard-basic" label="Name" name="name" variant="standard" />
</Grid>
<Grid item>
<Button type="submit" />
</Grid>
</Grid>
</form>
I was able to fix it by integrating the react-hook-form.
Here is the full code if anyone is interested.
import Grid from '#mui/material/Grid';
import Box from '#mui/material/Box';
import React, { useRef, useState } from 'react';
import { useForm } from "react-hook-form";
import TextField from '#mui/material/TextField';
import { Typography } from '#mui/material';
import Button from '#mui/material/Button';
import "../css/contact.css"
import { motion } from 'framer-motion';
import emailjs from '#emailjs/browser'
const Contact = ({ pageTransitions }) => {
const form = useRef();
const [successMessage, setSuccessMessage] = useState("");
const { register, handleSubmit, formState: { errors }, } = useForm();
const serviceID = "";
const templateID = "";
const userID = "";
const onSubmit = (data, r) => {
sendEmail(
serviceID,
templateID,
{
name: data.name,
email: data.email,
subject: data.subject,
message: data.message,
},
userID
)
r.target.reset();
}
// send email
const sendEmail = (serviceID, templateID, variables, userID) => {
emailjs.send(serviceID, templateID, variables, userID)
.then(() => {
setSuccessMessage("Sent successfully! I will contact you as soon as possible!");
}).catch(err => console.error(`Something went wrong ${err}`));
};
return (
<motion.div
initial='out'
animate='in'
exit='out'
variants={pageTransitions}>
<form ref={form} onSubmit={handleSubmit(onSubmit)}>
<Grid container padding={2} sx={{
boxShadow: 5
}} className="element center-form" >
<Grid item xs={12} sm={12} md={12}>
<Typography sx={{
textShadow: '2px 3px 5px rgb(0 0 0 / 50%)'
}} variant="h3">
Contact Me
</Typography>
<span className="error-message">
<p> {errors.name && errors.name.message} </p>
<p> {errors.email && errors.email.message} </p>
<p> {errors.subject && errors.subject.message} </p>
<p> {errors.message && errors.message.message} </p>
</span>
</Grid>
<Grid item xs={12} md={6}>
<Box>
<TextField sx={{
marginBottom: 2,
}} {
...register("name", {
required: "* Please enter your name",
maxLength: {
value: 20,
message: "Name must not be longer than 20 characters",
}
})
} fullWidth={true} id="standard-basic" label="Name" name="name" variant="standard" />
<br />
<TextField sx={{
marginBottom: 2
}} {
...register("email", {
required: "* Please enter a valid email",
pattern: {
value: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "* Invalid Email"
}
})
} fullWidth={true} id="standard-basic" label="Email" name="email" variant="standard" />
<br />
<TextField sx={{
marginBottom: 2
}} {
...register("subject", {
required: "* Please enter a subject",
maxLength: {
value: 20,
message: "* Subject must not be longer than 20 characters",
}
})
} fullWidth={true} id="standard-basic" label="Subject" name='subject' variant="standard" />
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{
marginTop: 13.2,
}}>
<TextField multiline
minRows={2}
maxRows={4} sx={{
marginBottom: 2,
width: '85%'
}} {
...register("message", {
required: "* Please enter a message",
})
} id="standard-basic" label="Message" name='message' variant="standard"
/>
</Box>
</Grid>
<Grid item xs={12} sm={12} md={12}>
<Button type="submit" className='button-shadow' sx={{
border: 1,
width: '85%',
}} variant="outlined">Send</Button>
</Grid>
</Grid>
</form>
</motion.div>
)
}
export default Contact

React <Switch> is not getting updated or changing its state

Hey guys I am learning react and have a form to edit book details. I am using django in the backend. I am not able to update the e-book which is a switch to turn on and off according to whether e-book is available or not. The switch works fine, but no data is being changed in the database. I am confused on how to work with the switch and saw lots of resources saying different things which is quite difficult to understand. Rest of the fields are getting updated correctly. Can someone tell me what changes I have to make in this switch to make it work??
code
import React, {useEffect, useState} from 'react'
import axiosInstance from '../../axios';
import { useForm, Controller } from 'react-hook-form';
//MaterialUI
import Button from '#material-ui/core/Button';
import CssBaseline from '#material-ui/core/CssBaseline';
import TextField from '#material-ui/core/TextField';
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
import { makeStyles } from '#material-ui/core/styles';
import Container from '#material-ui/core/Container';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Switch from '#material-ui/core/Switch';
import Select from '#material-ui/core/Select';
import InputLabel from '#material-ui/core/InputLabel';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
bookformbody: {
display: "flex",
backgroundColor: "#ffff",
height: "100vh",
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(3),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
function BookEdit() {
const initialFormData = Object.freeze({
id: '',
summary: '',
published_date: '',
e_book: false,
});
const [formData, updateFormData] = useState(initialFormData);
useEffect(() => {
axiosInstance.get('api/books/details/view/').then((res) => {
updateFormData({
...formData,
['summary']: res.data.summary,
['published_date']: res.data.published_date,
['e_book']: res.data.e_book,
});
console.log(res.data);
});
}, [updateFormData]);
const handleChange = (e) => {
if(e.target.name === "e_book"){
return(
updateFormData({
...formData,
[e.target.name]: e.target.checked
})
)
}
updateFormData({
...formData,
// Trimming any whitespace
[e.target.name]: e.target.value
});
};
const onSubmit = (data) =>{
let formData = new FormData();
formData.append('summary', data.summary);
formData.append('published_date', data.published_date);
formData.append('e_book', data.e_book);
axiosInstance.put('api/books/details/edit/', formData);
} ;
const classes = useStyles();
const { register, handleSubmit, control, errors } = useForm();
return (
<>
<Header />
<div className={classes.bookformbody}>
<SideBar />
<Container component="main" maxWidth="sm">
<CssBaseline />
<div className={classes.paper}>
<Typography component="h1" variant="h5">
Edit Book Details
</Typography>
<form className={classes.form} noValidate onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
id="summary"
label="Book Summary"
name="summary"
autoComplete="summary"
value={formData.summary}
onChange={handleChange}
inputRef={register({maxLength: 1000})}
multiline
rows={4}
/>
</Grid>
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
type="date"
label="Published Date"
name="published_date"
autoComplete="published_date"
value={formData.published_date}
onChange={handleChange}
inputRef={register({required: true})}
InputLabelProps={{
shrink: true,
}}
/>
</Grid>
<Grid item xs={12}>
<InputLabel id="e-book-switch">E-Book</InputLabel>
<Controller
name="e_book"
control={control}
as={
<Switch size="small"
id="e-book-switch"
type="checkbox"
name="e_book"
onChange={handleChange}
// inputRef={register}
value={formData.e_book}
checked={formData.e_book}
/>
}
/>
</Grid>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Update
</Button>
</form>
</div>
</Container>
</div>
</>
)
}
export default BookEdit;
You are passing props to Switch directly, but what you should do is to use Controller props, and Controller will then pass it down to Switch:
<Controller as={Switch} value={formData.e_book} ... />
Otherwise you need to use render, not as
This is actually described pretty good here:
https://react-hook-form.com/api/#Controller
UPDATE
Here is the sandbox where it is working fine with above changes - https://codesandbox.io/s/kind-nash-s94v0
What i did there is simply this:
<Controller
name="e_book"
control={control}
render={(props) => {
return (
<Switch
size="small"
id="e-book-switch"
type="checkbox"
name="e_book"
onChange={handleChange}
// inputRef={register}
value={formData.e_book}
checked={formData.e_book}
/>
);
}}
/>
You are initializing the value as a string at the beginning of BookEdit(). Try to set initialFormData like this:
const initialFormData = Object.freeze({
id: '',
summary: '',
published_date: '',
e_book: false,
});

React, Formik Field Arrays - mapping over repeatable fields

I'm trying to figure out how to use Formik field arrays in a react project.
I have one form (glossary) that has 3 Field Arrays within it (one for each of relatedTerms, templates and referenceMaterials).
Each of the field arrays is set out in a separate component. When I only used one of them, I had this working. Adding the next one has caused a problem that I can't solve.
My form has:
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,
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 "./RelatedTerms";
import ReferenceMaterials from "./ReferenceMaterials";
import Templates from "./Templates";
const allCategories = [
{value: 'one', label: 'One'},
{value: 'two', label: 'Two'},
];
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>
</DialogContentText>
<Formik
initialValues={{ term: "", definition: "", category: [], context: "", relatedTerms: [], templates: [], referenceMaterials: [] }}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
firestore.collection("glossary").doc().set({
...values,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
})
.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"),
// templates: Yup.string()
// .required("Required"),
// referenceMaterials: 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>
<Divider style={{marginTop: "20px", marginBottom: "20px"}}></Divider>
<Box>
<Typography variant="subtitle2">
Add a related term
</Typography>
<FieldArray name="relatedTerms" component={RelatedTerms} />
</Box>
<Box>
<Typography variant="subtitle2">
Add a reference document
</Typography>
<FieldArray name="referenceMaterials" component={ReferenceMaterials} />
</Box>
<Box>
<Typography variant="subtitle2">
Add a template
</Typography>
<FieldArray name="templates" component={Templates} />
</Box>
<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>
We appreciate your contribution.
</DialogContentText>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleClose}
>
Close
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</DialogContent>
</React.Fragment>}
</Dialog>
</React.Fragment>
);
}
export default Glossary;
Then, each subform is as follows (but replacing relatedTerms for templates or referenceMaterials).
import React from "react";
import { Formik, Field } from "formik";
import { withStyles } from '#material-ui/core/styles';
import {
Button,
LinearProgress,
MenuItem,
FormControl,
InputLabel,
FormControlLabel,
TextField,
Typography,
Box,
Grid,
Checkbox,
} from '#material-ui/core';
import MuiTextField from '#material-ui/core/TextField';
import {
fieldToTextField,
TextFieldProps,
Select,
Switch,
} from 'formik-material-ui';
const initialValues = {
title: "",
description: "",
source: ""
};
class RelatedTerms 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}>
<TextField
label="Title"
name={`relatedTerms.${index}.title`}
placeholder=""
// className="form-control"
// value={values.title}
margin="normal"
style={{ width: "100%"}}
onChange={e => {
parentForm.setFieldValue(
`relatedTerms.${index}.title`,
e.target.value
);
}}
>
</TextField>
<TextField
label="Description"
name={`relatedTerms.${index}.description`}
placeholder="Describe the relationship"
// value={values.description}
onChange={e => {
parentForm.setFieldValue(
`relatedTerms.${index}.description`,
e.target.value
);
}}
// onBlur={handleBlur}
// helperText={(errors.definition && touched.definition) && errors.definition}
margin="normal"
style={{ width: "100%"}}
/>
<Button
variant="outlined"
color="secondary"
size="small"
onClick={() => parentProps.remove(index)}
>
Remove this term
</Button>
</div>
);
})}
<Button
variant="contained"
color="secondary"
size="small"
style={{ marginTop: "5vh"}}
onClick={() => parentProps.push(initialValues)}
>
Add a related term
</Button>
</div>
);
}}
/>
);
}
}
export default RelatedTerms;
Then when I try to render the data submitted in the form, I have:
import React, { useState, useEffect } from 'react';
import {Link } from 'react-router-dom';
import Typography from '#material-ui/core/Typography';
import ImpactMetricsForm from "./Form";
import firebase, { firestore } from "../../../../firebase.js";
import { makeStyles } from '#material-ui/core/styles';
import clsx from 'clsx';
import ExpansionPanel from '#material-ui/core/ExpansionPanel';
import ExpansionPanelDetails from '#material-ui/core/ExpansionPanelDetails';
import ExpansionPanelSummary from '#material-ui/core/ExpansionPanelSummary';
import ExpansionPanelActions from '#material-ui/core/ExpansionPanelActions';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import Chip from '#material-ui/core/Chip';
import Button from '#material-ui/core/Button';
import Divider from '#material-ui/core/Divider';
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
marginTop: '8vh',
marginBottom: '5vh'
},
heading: {
fontSize: theme.typography.pxToRem(15),
},
heading2: {
fontSize: theme.typography.pxToRem(15),
fontWeight: "500",
marginTop: '3vh',
marginBottom: '1vh',
},
secondaryHeading: {
fontSize: theme.typography.pxToRem(15),
color: theme.palette.text.secondary,
textTransform: 'capitalize'
},
icon: {
verticalAlign: 'bottom',
height: 20,
width: 20,
},
details: {
alignItems: 'center',
},
column: {
flexBasis: '20%',
},
columnBody: {
flexBasis: '70%',
},
helper: {
borderLeft: `2px solid ${theme.palette.divider}`,
padding: theme.spacing(1, 2),
},
link: {
color: theme.palette.primary.main,
textDecoration: 'none',
'&:hover': {
textDecoration: 'underline',
},
},
}));
const Title = {
fontFamily: "'Montserrat', sans-serif",
fontSize: "4vw",
marginBottom: '2vh'
};
const Subhead = {
fontFamily: "'Montserrat', sans-serif",
fontSize: "calc(2vw + 1vh + .5vmin)",
marginBottom: '2vh',
marginTop: '8vh',
width: "100%"
};
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
}
const GlossaryTerms = () => {
const glossaryTerms = useGlossaryTerms()
const classes = useStyles();
return (
<div style={{ marginLeft: "3vw"}}>
<div className={classes.root}>
{glossaryTerms.map(glossaryTerm => {
return (
<ExpansionPanel defaultcollapsed>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1c-content"
id="panel1c-header"
>
<div className={classes.column}>
<Typography className={classes.heading}>{glossaryTerm.term}</Typography>
</div>
<div className={classes.column}>
{glossaryTerm.category.map(category => (
<Typography className={classes.secondaryHeading}>
{category.label}
</Typography>
)
)}
</div>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.details}>
<div className={clsx(classes.columnBody)}>
<div>
<Typography variant="subtitle2" className={classes.heading2}>Meaning</Typography>
<Typography>{glossaryTerm.definition}</Typography>
</div>
<div>
<Typography variant="subtitle2" className={classes.heading2}>Context</Typography>
<div>
<Typography>{glossaryTerm.context}</Typography>
</div>
<div className={clsx(classes.helper)}>
<div>
<Typography variant="caption">Related Terms</Typography>
{glossaryTerm.relatedTerms.map(relatedTerm => (
<Typography variant="body2" className="blogParagraph" key={relatedTerm.id}>
{relatedTerm.title}
</Typography>
))}
</div>
<div>
<Typography variant="caption" >Related Templates</Typography>
{glossaryTerm.templates.map(template => (
<Typography variant="body2" className="blogParagraph" key={template.id}>
{template.title}
</Typography>
))}
</div>
<div>
<Typography variant="caption">Related Reference Materials</Typography>
{glossaryTerm.referenceMaterials.map(referenceMaterial => (
<Typography variant="body2" className="blogParagraph" key={referenceMaterial.id}>
{referenceMaterial.title}
</Typography>
))}
</div>
</div>
</ExpansionPanelDetails>
<Divider />
<ExpansionPanelActions>
{glossaryTerm.attribution}
</ExpansionPanelActions>
</ExpansionPanel>
)
})}
</div>
</div>
);
}
export default GlossaryTerms;
When I try this using only the relatedTerms field array, I can submit data in the form and render the list.
When I add in the next two Field Array components for Templates and ReferenceMaterials, I get an error that says:
TypeError: glossaryTerm.referenceMaterials.map is not a function
Each of the 3 field arrays is a duplicate, where I've only changed the name of the value in the main form. You can see from the screen shot attached that the data within each map from the form fields is the same for each of relatedTerms, templates and referenceMaterials. When I comment out templates and referenceMaterials from the rendered output, everything renders properly. When I comment out relatedTerms and try to render either templates or referenceMaterials, I get the error I reported.
If I remove the templates and referenceMaterials map statements from the rendered output, I can use the form with all 3 field arrays in it. They save properly in firebase. I just can't display them using the method that works for relatedTerms.
Everything seems ok with your code. I suspect that the problem is in the data coming from firebase in useGlossaryTerms, some entries in the glossary collection may not have referenceMaterials or templates fields (maybe from a previous form submit that did not have those yet).
You could :
Run a migration script on the collection to add defaults for those fields if they don't exist.
Add defaults on client side :
firebase
.firestore()
.collection("glossary")
.orderBy('term')
.onSnapshot(snapshot => {
const glossaryTerms = snapshot.docs.map(doc => {
const data = doc.data();
return {
id: doc.id,
...data,
referenceMaterials: data.referenceMaterials || [],
templates: data.templates || []
};
}
setGlossaryTerms(glossaryTerms)
})
On the client side, check if those fields exists before rendering :
{
glossaryTerm.templates ? (
<div>
<Typography variant="caption" >Related Templates</Typography>
{glossaryTerm.templates.map(template => (
<Typography variant="body2" className="blogParagraph" key={template.id}>
{template.title}
</Typography>
))}
</div>
) : null
}

Formik Material UI React - Autocomplete - uncontrolled to controlled state

I'm trying to figure out how to follow the instructions in the documentation for the Autocomplete field of the Formik, Material UI, React tool here.
The example given in the documentation is:
import { Autocomplete } from 'formik-material-ui-lab';
const options = [{ title: 'The Shawshank Redemption', year: 1994 }, ...]
<Field
name="name"
component={Autocomplete}
options={options}
getOptionLabel={(option: Movie) => option.title}
style={{ width: 300 }}
renderInput={(params: AutocompleteRenderInputParams) => (
<TextField
{...params}
error={touched['name'] && !!errors['name']}
helperText={errors['name']}
label="Autocomplete"
variant="outlined"
/>
)}
/>;
No clues are given as to the meaning of Movie where it is used in getOptionLabel. When I try to use this, Movie is underlined as is AutocompleteRenderInputParams in the renderInput object. I don't know why.
I have seen this post which tries an alternative approach, but I can't get that to work either.
I have a form, with two Autocomplete fields. Currently, it looks like this.
When I try to use the form, the submit button hangs and the console log says:
Material-UI: The getOptionLabel method of Autocomplete returned
undefined instead of a string for "".
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import firebase, {firestore} from '../../../firebase';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import Box from '#material-ui/core/Box';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
import {
Formik, Form, Field, ErrorMessage,
} from 'formik';
import * as Yup from 'yup';
import { Autocomplete, ToggleButtonGroup } from 'formik-material-ui-lab';
import { Switch } from 'formik-material-ui';
const styles = {
};
const allCategories = [
{value: 'culture', label: 'Culture'},
{value: 'other', label: 'Other'},
];
const sharingOptions = [
{value: 'open', label: 'Openly'},
{value: 'me', label: 'Only me'},
];
function Contact(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 an Impact Metric
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
{!isSubmitionCompleted &&
<React.Fragment>
<DialogTitle id="form-dialog-title">Create an Impact Metric</DialogTitle>
<DialogContent>
<DialogContentText>
test form.
</DialogContentText>
<Formik
initialValues={{ title: "", category: "", sharing: "" }}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
firestore.collection("testing").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'),
sharing: 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"
component={Autocomplete}
options={allCategories}
getOptionLabel={option => option.label}
style={{ width: 300 }}
renderInput={(params: AutocompleteRenderInputParams) => (
<TextField
{...params}
error={touched['category'] && !!errors['category']}
helperText={
touched['category'] && errors['category']
}
label="Select Category"
variant="outlined"
/>
)}
/>
</Box>
<Box margin={1}>
<Field
name="sharing"
component={Autocomplete}
options={sharingOptions}
getOptionLabel={option => option.label}
style={{ width: 300 }}
renderInput={(params: AutocompleteRenderInputParams) => (
<TextField
{...params}
error={touched['sharing'] && !!errors['sharing']}
helperText={
touched['sharing'] && errors['sharing']
}
label="Select Sharing Option"
variant="outlined"
/>
)}
/>
</Box>
<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>
test
</DialogContentText>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleClose}
>
Close
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</DialogContent>
</React.Fragment>}
</Dialog>
</React.Fragment>
);
}
export default withStyles(styles)(Contact);
Can anyone see how to get the autocomplete working with formik, material ui in line with the documentation published at the link above?
I also tried using the regular select form input. This is the form field:
<Box margin={1}>
<Field
component={TextField}
type="text"
name="category"
label="Category"
select
variant="outlined"
helperText="Select a category"
margin="normal"
style={{ width: "100%"}}
InputLabelProps={{
shrink: true,
}}
>
{allCategories.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Field>
When I try this, I get a warning in the console that says:
instrument.ts:129 Material-UI: You have provided an out-of-range value `undefined` for the select component.
Consider providing a value that matches one of the available options or ''
This warning doesn't make any sense - the form renders with a menu correctly populated.
I also get an error that says:
index.js:1 Warning: A component is changing an uncontrolled input of
type undefined to be controlled. Input elements should not switch from
uncontrolled to controlled (or vice versa). Decide between using a
controlled or uncontrolled input element for the lifetime of the
component. More info
In relation to that error, I have seen this post, which recommends using value (rather than input - which I do) and defining all the initial values as a type. For me, they are all strings, although I tried replacing the select fields with empty arrays. In both alternatives, the same error message is returned in the console.
At this point - I don't care which of autocomplete or select I use, I just want to get one of them working.
It is interesting that in both cases (using select and autocomplete) the console logs warnings that say:
Material-UI: You have provided an out-of-range value `undefined` for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are `one`, `two`.
(anonymous) # 0.chunk.js:141301
0.chunk.js:141301 Material-UI: You have provided an out-of-range value `undefined` for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are `a`, `b`, `c`, `d`.
BUT, only one instance of the error that says:
A component is changing an uncontrolled input of type undefined to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: react-website -controlled-components
in input (created by ForwardRef(SelectInput))
in ForwardRef(SelectInput) (created by ForwardRef(InputBase))
in div (created by ForwardRef(InputBase))
in ForwardRef(InputBase) (created by WithStyles(ForwardRef(InputBase)))
in Wi
This error points to the category select form input.
I also tried adding the gender select form field from this code sandbox to my form to see if I could get this working. When I comment out the category and sharing fields described above, and add a gender field with a default value of an empty string, the form loads.
The field is:
<Field
name="gender"
label="Gender"
options={[
{ value: "Male", label: "Male" },
{ value: "Female", label: "Female" },
{ value: "Other", label: "Other" }
]}
component={Select}
/>
The select field for gender appears but is about 1cm wide and the options menu does not populate with options, I can't select anything. BUT the form does load to firebase with an empty string in the gender field. That's progress but not enough to move forward.
The same code sandbox shows a field that uses Autocomplete. I tried to adapt it and use it in my form as follows:
<Field
name="gender"
label="Gender"
options={sharingOptions}
component={Autocomplete}
textFieldProps={{
label: sharingOptions.label
}}
/>
When I try that, I get an error that says:
TypeError: renderInput is not a function
This error message makes no sense to me because I'm not using renderInput anywhere in the form.
When I try:
<Box margin={1}>
<Field
component={Select}
type="text"
name="category"
label="Impact Category"
select
variant="outlined"
helperText="Select a category"
margin="normal"
style={{ width: "100%"}}
InputLabelProps={{
shrink: true,
}}
>
{allCategories.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Field>
</Box>
I get no errors and can save the form with the option details. However, this does not actually solve the problem about why Autocomplete will not work. This is also not using the Select field as shown in the linked documentation. So I'm no clearer on why this works or why the method shown in the documentation does not work.
NEXT ATTEMPT
Using the autocomplete example in this codesandbox as a guide, I tried:
<Field
name="autocomplete"
multiple
component={Autocomplete}
options={sharingOptions}
getOptionLabel={(option: any) => option.title}
style={{width: 300}}
renderInput={(params: AutocompleteRenderInputParams) => (
<MuiTextField
{...params}
error={touched['autocomplete'] && !!errors['autocomplete']}
helperText={touched['autocomplete'] && errors['autocomplete']}
label="Autocomplete"
variant="outlined"
/>
)}
/>
As with the earlier example, my code editor underlines the value "any" where it appears in getOptionLabel and it also underlines AutocompleteRenderInputParams. I can't find any documentation explaining what these elements of the form field mean or do. In any event, I have imported AutocompleteRenderInputParams as shown in the code sandbox.
I made the initial value of the autocomplete field in my form an empty array - although I note the code sandbox does not set an initial value in this example. When I try removing the initial value of autocomplete, I get the same errors as are generated when the initial value is an empty array, but I also get a warning in the console that says:
Warning: value for autocomplete is not an array, this can caused
unexpected behaviour
When I try this code, my console logs the following errors:
TypeError: Cannot read property 'toLowerCase' of undefined
Material-UI: The getOptionLabel method of Autocomplete returned
undefined instead of a string for {"value":"open","label":"Open
"}.
Working example:
Demo
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import { Formik, Field } from "formik";
import { Autocomplete } from "formik-material-ui-lab";
import { TextField } from "#material-ui/core";
const options = [
{ title: "The Shawshank Redemption", year: 1994 },
{ title: "Inglourious Basterds", year: 2009 },
{ title: "Snatch", year: 2000 },
{ title: "3 Idiots", year: 2009 },
{ title: "Monty Python and the Holy Grail", year: 1975 }
];
function App() {
return (
<Formik
initialValues={{
autocomplete: null
}}
>
{() => (
<Field
name="autocomplete"
component={Autocomplete}
options={options}
getOptionLabel={(option) => option.title}
style={{ width: 300 }}
renderInput={(params) => (
<TextField {...params} label="Autocomplete" variant="outlined" />
)}
/>
)}
</Formik>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
For others who are stuck, this works - although I don't understand why. The underlined errors noted in the post above remain and I don't know how to solve for them.
Sharing this as a way forward - rather than a good solution.
import React, { useState } from 'react';
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 ToggleButton from '#material-ui/lab/ToggleButton';
import FormatAlignLeftIcon from '#material-ui/icons/FormatAlignLeft';
import FormatAlignCenterIcon from '#material-ui/icons/FormatAlignCenter';
import FormatAlignRightIcon from '#material-ui/icons/FormatAlignRight';
import FormatAlignJustifyIcon from '#material-ui/icons/FormatAlignJustify';
import {
Formik, Form, Field, ErrorMessage,
} from 'formik';
import * as Yup from 'yup';
// import { Autocomplete, ToggleButtonGroup } from 'formik-material-ui-lab';
import {
Autocomplete,
ToggleButtonGroup,
AutocompleteRenderInputParams,
} from 'formik-material-ui-lab';
import {
fieldToTextField,
TextFieldProps,
Select,
Switch,
} from 'formik-material-ui';
const allCategories = [
{value: 'one', label: 'Col'},
{value: 'two', label: 'Com'},
];
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 a Defined Term
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
{!isSubmitionCompleted &&
<React.Fragment>
<DialogTitle id="form-dialog-title">Create </DialogTitle>
<DialogContent>
<DialogContentText>
</DialogContentText>
<Formik
initialValues={{ term: "", definition: "", category: [], attribution: true, attributionRegion: '', context: "", relatedTerms: "", linkedTemplates: "", referenceMaterials: "" }}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
firestore.collection("glossary").doc().set({
...values,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
})
.then(() => {
setSubmitionCompleted(true);
});
}}
validationSchema={Yup.object().shape({
term: Yup.string()
.required('Required'),
definition: Yup.string()
.required('Required'),
category: Yup.string()
.required('Required'),
attribution: Yup.boolean()
.required('Required'),
context: 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="How is it 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}>
<Typography component="div" style={{ marginTop: "5vh", marginBottom: "5vh"}}>
Choose)?
<Grid component="label" container alignItems="center" spacing={1}>
<Grid item>Attribution</Grid>
<Grid item>
<Field
component={Switch}
name="attribution"
type="checkbox"
>
</Field>
</Grid>
<Grid item>Anonymous</Grid>
</Grid>
</Typography>
</Box>
<Box margin={1}>
<Field
name="category"
multiple
component={Autocomplete}
options={allCategories}
getOptionLabel={(option: any) => option.label}
style={{width: 300}}
renderInput={(params: AutocompleteRenderInputParams) => (
<MuiTextField
{...params}
error={touched['autocomplete'] && !!errors['autocomplete']}
helperText={touched['autocomplete'] && errors['autocomplete']}
label="Category"
variant="outlined"
/>
)}
/>
</Box>
<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>
Thank you </DialogContentText>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleClose}
>
Close
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</DialogContent>
</React.Fragment>}
</Dialog>
</React.Fragment>
);
}
export default withStyles(styles)(Glossary);

Categories

Resources