React, Formik Field Arrays - mapping over repeatable fields - javascript

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
}

Related

Form validation with Yup (Checkbox) doesn't work

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

Cannot read properties of null (reading 'result') error in my comments section implementation function

In my application, I have a Meal object that has a comment section attached to it. I get an error when trying to make a comment. There is the code:
import React, { useState } from 'react';
import { Card, CardActions, CardContent, CardMedia, Button, Typography } from '#material-ui/core/';
import ThumbUpAltIcon from '#material-ui/icons/ThumbUpAlt';
import DeleteIcon from '#material-ui/icons/Delete';
import MoreHorizIcon from '#material-ui/icons/MoreHoriz';
import ThumbUpAltOutlined from '#material-ui/icons/ThumbUpAltOutlined';
import moment from 'moment';
import {useDispatch, useSelector} from 'react-redux';
import { Modal, Box, TextField } from "#material-ui/core";
import useStyles from './styles';
import {deleteMeal, getMeals, likeMeal} from '../../../actions/meals';
import { postCommentOnMeal } from '../../../actions/workouts';
const Meal=({meal, setCurrentId})=> {
const classes=useStyles();
const dispatch=useDispatch();
const user = JSON.parse(localStorage.getItem('profile'));
const Likes = () => {
if (meal.likes && meal.likes.length > 0) {
return meal.likes.find((like) => like === (user?.result?.googleId || user?.result?._id))
? (
<><ThumbUpAltIcon fontSize="small" /> {meal.likes.length > 2 ? `You and ${meal.likes.length - 1} others` : `${meal.likes.length} like${meal.likes.length > 1 ? 's' : ''}` }</>
) : (
<><ThumbUpAltOutlined fontSize="small" /> {meal.likes.length} {meal.likes.length === 1 ? 'Like' : 'Likes'}</>
);
}
return <><ThumbUpAltOutlined fontSize="small" /> Like</>;
};
const [open, setOpen] = React.useState(false);
const [currentMeal, setcurrentMeal] = React.useState("");
const handleOpen = (id) => {
setcurrentMeal(id);
setOpen(true);
};
const handleClose = () => setOpen(false);
const [comment, setComment] = useState("");
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
border: "2px solid #000",
boxShadow: 24,
p: 4,
};
const currentUser = useSelector((state) => state.auth);
function addComment() {
const data = {
comment,
userId: currentUser.authData.result._id,
userName: currentUser.authData.result.name,
mealId: currentMeal,
};
dispatch(postCommentOnMeal(data));
dispatch(getMeals());
handleClose();
}
function getComments() {
return (
<>
{meal && meal.comments.length > 0
? meal.comments.map((item) => {
return (
<div
style={{
marginTop: 10,
backgroundColor: "grey",
padding: 2,
borderRadius: 4,
}}
>
<h5 style={{ marginBottom: -10 }}>{item.commentor}</h5>
<p>{item.comment}</p>
</div>
);
})
: "No comments yet"}
</>
);
}
return (
<>
<Card className={classes.card}>
<CardMedia className={classes.media} image={meal.selectedFile|| 'https://user-images.githubusercontent.com/194400/49531010-48dad180-f8b1-11e8-8d89-1e61320e1d82.png'} title={meal.category}/>
<div className={classes.overlay}>
<Typography variant="h6">{meal.calories}</Typography>
<Typography variant="body2">{moment(meal.createdAt).fromNow()}</Typography>
</div>
{(user?.result?.googleId===meal?.creator || user?.result?._id===meal?.creator) && (
<div className={classes.overlay2}>
<Button style={{color:'white'}} size="small" onClick={()=>{
setCurrentId(meal._id)
}}>
<MoreHorizIcon fontSize="medium"/>
</Button>
</div>
)}
<div className={classes.details}>
<Typography className={classes.title} gutterBottom variant="h5" component="h2">{meal.name}</Typography>
</div>
<Typography className={classes.difficulty} variant="body2" color="textSecondary" component="p">{meal.category}</Typography>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">{meal.calories}</Typography>
</CardContent>
<CardActions className={classes.cardActions}>
<Button size='small' color="primary" disabled={!user?.result} onClick={()=>{dispatch(likeMeal(meal._id))}}>
<Likes/>
</Button>
<Button
size="small"
color="primary"
disabled={!user?.result}
onClick={() => {
handleOpen(meal._id);
}}
>
Comments
</Button>
{(user?.result?.googleId===meal?.creator || user?.result?._id===meal?.creator) && (
<Button size='small' color="primary" onClick={()=>{dispatch(deleteMeal(meal._id))}}>
<DeleteIcon fontSize="small"/>
Delete
</Button>
)}
</CardActions>
</Card>
<Modal
className={classes.modal}
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
{getComments()}
<TextField
margin="normal"
name="comment"
variant="outlined"
label="Comment"
fullWidth
value={comment}
onChange={(e) => {
setComment(e.target.value);
}}
/>
<Button
margin="normal"
variant="contained"
color="primary"
onClick={addComment}
>
POST
</Button>
</Box>
</Modal>
</>
);
}
export default Meal;
The error occurs at this line: userId: currentUser.authData.result._id, in the addComment() function and it says: Uncaught TypeError: Cannot read properties of null (reading 'result').
Could you help me?

How to pass data from input field to parent component using context API in React?

I have an input field in child component. If you type in valid wallet address it should display wallet balance in parent component.
I don't know how to pass that e.target.value we are reading from input field.
I tried to do this function in parent component - Dashboard.
const [walletBalance, setWalletBalance] = useState('');
const getWalletBalance = async () => {
try {
const web3 = new Web3(Web3.givenProvider || 'ws://localhost:3000');
for (const wallet of dummyWallets) {
const balance = await web3.eth.getBalance(wallet.address);
console.log(balance);
const balanceConvertedFromWeiToEth = await Web3.utils.fromWei(balance, 'ether');
const shortenedBalance =
balanceConvertedFromWeiToEth.length >= 10
? balanceConvertedFromWeiToEth.slice(0, 10)
: balanceConvertedFromWeiToEth;
setWalletBalance(shortenedBalance);
}
} catch (error) {
console.error(error);
throw new Error('No wallet found');
}
};
render (
<p>Wallet balance: {walletBalance}</p>
and then pass this function down to child component with input field (need to prop drill 3 times for that) and in child component say:
const [inputText, setInputText] = useState('');
const handleInputChange = (e) => {
setInputText(e.target.value);
if (inputText === wallet.address) {
getWalletBalance()
}
};
render (
<input value={inputText} onChange={handleInputChange} />
)
It didn't work. No errors. Just nothing happens.
Then I thought that I need to do the other way around: Instead of trying to pass getWalletBalance() down as props, I need to read data from input field and pass that data to parent component, because parent needs to have access to the input field and read from it to be able to display wallet balance in parent.
I've also tried to create context:
import React, { useState, createContext } from 'react';
import { dummyWallets } from '../mocks/dummyWallets';
import Web3 from 'web3';
export const WalletContext = createContext();
// eslint-disable-next-line react/prop-types
export const TransactionProvider = ({ children }) => {
const [inputText, setInputText] = useState('');
const [walletBalance, setWalletBalance] = useState('');
const handleInputChange = async (e) => {
setInputText(e.target.value);
const web3 = new Web3(Web3.givenProvider || 'ws://localhost:3000');
for (const wallet of dummyWallets) {
if (inputText === wallet.address) {
const balance = await web3.eth.getBalance(wallet.address);
console.log(balance);
const balanceConvertedFromWeiToEth = await Web3.utils.fromWei(balance, 'ether');
const shortenedBalance =
balanceConvertedFromWeiToEth.length >= 10
? balanceConvertedFromWeiToEth.slice(0, 10)
: balanceConvertedFromWeiToEth;
setWalletBalance(shortenedBalance);
}
}
};
return (
<WalletContext.Provider
value={{ walletBalance, handleInputChange, inputText, setInputText,
setWalletBalance }}>
{children}
</WalletContext.Provider>
);
};
Then pass handleInputChange to onChange in child component and inputText as value, then use walletBalance in parent component.
All context things walletBalance, handleInputChange, inputText, setInputText and setWalletBalance threw undefined error.
Here is child component consuming data from context:
import React, { FC, useState, useContext } from 'react';
import { Formik, Form, Field } from 'formik';
import { Button, InputAdornment, Box, Grid } from '#material-ui/core';
import TextField from '#eyblockchain/ey-ui/core/TextField';
import { Search } from '#material-ui/icons';
import SvgEthereum from '../../../images/icon/Ethereum';
import { makeStyles } from '#material-ui/styles';
import { Theme } from '#material-ui/core';
import { dummyWallets } from '../../../shared/mocks/dummyWallets';
import Web3 from 'web3';
import { WalletContext } from '../../../shared/context/WalletContext';
const etherIconBorderColor = '#eaeaf2';
const useStyles = makeStyles((theme: Theme) => ({
etherIconGridStyle: {
padding: '16px 0 0 0'
},
etherIconStyle: {
fontSize: '220%',
textAlign: 'center',
color: theme.palette.primary.light,
borderTop: `solid 1px ${etherIconBorderColor}`,
borderBottom: `solid 1px ${etherIconBorderColor}`,
borderLeft: `solid 1px ${etherIconBorderColor}`,
padding: '5px 0 0 0',
[theme.breakpoints.down('sm')]: {
fontSize: '150%',
lineHeight: '43px'
}
},
buttonStyle: {
paddingTop: theme.spacing(2)
},
searchIconStyle: {
color: theme.palette.primary.light
}
}));
const onSubmitHandler = () => {
return;
};
const SearchInputForm: FC<any> = (props) => {
const { walletBalance, handleInputChange, inputText } = useContext(WalletContext);
const classes = useStyles();
return (
<Formik
onSubmit={onSubmitHandler}
initialValues={{ name: '' }}
render={({ values }) => {
return (
<Form>
<Grid container>
<Grid item xs={1} className={classes.etherIconGridStyle}>
<Box className={classes.etherIconStyle}>
<SvgEthereum />
</Box>
</Grid>
<Grid item xs={11}>
<Field
required
name="name"
placeholder={props.placeholdervalue}
value={inputText}
component={TextField}
onChange={handleInputChange}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Search fontSize="large" className={classes.searchIconStyle} />
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12}>
<Box
display="flex"
justifyContent="flex-end"
alignItems="flex-end"
className={classes.buttonStyle}>
<Button type="submit" variant="contained" color="secondary" size="large" disabled>
Create Alert
</Button>
</Box>
</Grid>
</Grid>
</Form>
);
}}
/>
);
};
export default SearchInputForm;
Here is parent component where I pull walletBalance from context:
const Dashboard: FC<any> = () => {
const { walletBalance } = useContext(WalletContext);
const classes = useStyles();
return (
<>
<Container maxWidth={false} className={classes.dashboardContainer}>
<Grid>
<Box data-testid="alert-page-heading">
<Typography variant="h3">Alerts</Typography>
</Box>
<Box data-testid="alert-page-subheading">
<Typography className={classes.subHeadingStyle}>
Monitor the Web 3.0 for interesting blockchain events
</Typography>
</Box>
</Grid>
<Grid container spacing={4}>
<Grid item xs={12} sm={8}>
<Box className={classes.dashboardCard}>
<Box className={classes.searchBox}>
<SearchTabs />
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between'
}}>
<Box
sx={{
height: '60%',
padding: '1%',
fontSize: 20
}}>
<a href="http://localhost:3000/" className={classes.backToAlertsLink}>
Back to my alerts
</a>
<p className={classes.boldText}>Recent transactions</p>
</Box>
<Box sx={{ height: '60%', padding: '4%', fontSize: 20 }}>
<span>Wallet Balance</span>
<span className={classes.balanceNumber}>{walletBalance} ETH</span>
</Box>
</Box>
</Box>
</Grid>
<Grid item xs={12} sm={4}>
<Box className={classes.dashboardCard}>
<Box sx={{ height: '60%', padding: '2%', fontSize: 20, fontWeight: 'bold' }}>
Top 10 alerts
</Box>
</Box>
</Grid>
</Grid>
</Container>
</>
);
};
export default Dashboard;
The problem is that I pull handleChange from context and apply to input field in child component to read the value from the input and then pull walletBalance from context in parent but that walletBalance is not updated (doesn't contain input value in it).
Can someone please share knowledge on how to read data from child component input field and display something from that in parent component?
Any advice is appreciated. Thank you.

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

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