I'm developing a multistep form in ReactJs with Bootstrap, using Formik and Yup.
Actually, I have 2 problems here, despite I've read part of Formik and Yup documentation, I haven't found the answer yet.
I change my select fields' values, but the validationSchema simply ignores these values and only the initialValues are submitted to this validation.
Before validating my fields' values through validationSchema, I have to check whether the select option is not the first one (this first option is equivalent to "choose your option" for each field, and when the user submits it, it's value should be empty for the schema), it's possible to be done without validationSchema, by creating a validate function out of Formik, but I want to use Formik because I want a prettier way to display the errors as it does. I've tried plenty of stuff around, but I haven't gotten any answer until now.
Here is the Step 1 Form Code**
import React, { Component } from 'react';
import Form from 'react-bootstrap/Form';
import {ErrorMessage, Formik, validateYupSchema} from "formik";
import Col from 'react-bootstrap/Col';
import InputGroup from 'react-bootstrap/InputGroup';
import Button from 'react-bootstrap/Button';
import * as yup from 'yup';
import PropTypes from 'prop-types';
import {Categories} from './categoriesList.jsx';
import {Brands} from './brandsList';
import {ProductTypes} from './productTypesList';
import NewProduct from './newProductComponent.jsx';
let schema = yup.object().shape({
productCategory: yup.string().required(),
productBrand: yup.string().required(),
productType: yup.string().required()
});
class FormProducts1 extends Component {
reset = () => {
this.props.handleReset1();
}
render() {
const {values, handleChange, handleReset1} = this.props;
const CategoryOptions = Categories,
MakeItemCategory = function(itemCategory) {
return <option value={itemCategory} key={itemCategory}>{itemCategory}</option>;
};
const BrandOptions = Brands,
MakeItemBrand = function(itemBrand) {
return <option value={itemBrand} key={itemBrand}>{itemBrand}</option>;
};
const ProductOptions = ProductTypes,
MakeItemProduct = function(itemProduct) {
return <option value={itemProduct} key={itemProduct}>{itemProduct}</option>;
};
return (
<Formik
initialValues = {{
productCategory:'',
productBrand: '',
productType: ''
}}
validationSchema = {schema}
onSubmit = {
values => {console.log(values)}
}
>
{({
handleSubmit,
handleBlur,
touched,
isInvalid, //defines what is invalid
errors //catch up the errors to be displayed
}) => (
<Form onSubmit={handleSubmit}>
<Form.Group as={Col} md="12" controlId="formProduct.ProductCategory">
<Form.Label>Categoria do Produto</Form.Label>
<Form.Control
name="productCategory"
as="select"
onChange={handleChange('productCategory')}
value={values.productCategory}
onBlur={() => handleBlur('productCategory',true)}
isInvalid={touched.productCategory && errors.productCategory}
>
{CategoryOptions.map(MakeItemCategory)}
</Form.Control>
<Form.Control.Feedback type="invalid">{errors.productCategory}</Form.Control.Feedback>
</Form.Group>
<Form.Group as={Col} md="12" controlId="formProduct.ProductBrand">
<Form.Label>Marca do Produto</Form.Label>
<Form.Control
name="productBrand"
as="select"
onChange={handleChange('productBrand')}
value={values.productBrand}
onBlur = {() => handleBlur('productBrand',true)}
isInvalid = {touched.productBrand && errors.productBrand} >
{BrandOptions.map(MakeItemBrand)}
</Form.Control>
<Form.Control.Feedback type="invalid">{errors.productBrand}</Form.Control.Feedback>
</Form.Group>
<Form.Group as={Col} md="12" controlId="formProduct.ProductType">
<Form.Label>Tipo do Produto</Form.Label>
<Form.Control
name="productType"
as="select"
onChange={handleChange('productType')}
value={values.productType}
onBlur = {() => handleBlur('productType',true)}
isInvalid={touched.productType && errors.productType}>
{ProductOptions.map(MakeItemProduct)}
</Form.Control>
<Form.Control.Feedback type="invalid">{errors.productType}</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="buttonsCategory" style={this.formatForm}>
<Button variant="secondary m-2" type="reset" style={this.styleAddButton} onClick={this.reset}>
Limpar
</Button>
<Button variant="primary" type="submit" style={this.styleAddButton}>
Continuar
</Button>
</Form.Group>
</Form>
)}
</Formik>
);
}
}
export default FormProducts1;
*****************Here is the Steps' Container Component Code *******************
import React, { Component } from 'react';
import FormProducts1 from './formProducts1';
import FormProducts2 from './formProducts2';
import FormProducts3 from './formProducts3';
import FormProductsSuccess from './formProductsSuccess';
import PropTypes from "prop-types";
class NewProduct extends Component {
state = {
step:1,
productCategory:'',
productBrand:'',
productType:'',
productName:'',
productPrice:0,
productCode:'',
productDescription:''
}
formatForm = {
marginTop:15
};
styleAddButton = {
fontSize:15,
fontWeight:"bold"
};
//Proceed to the next step
nextStep = () => {
const {step} = this.state
this.setState({
step: step + 1
});
}
//Go back to previous step <div>
prevStep = () => {
const {step} = this.state
this.setState({
step: step - 1
});
}
//Handle fields change
handleChange = input => e => {
this.setState({
[input]: e.target.value
});
}
toCurrency(number) {
const formatter = new Intl.NumberFormat("pt-BR", {
style: "decimal",
currency: "BRL"
});
return formatter.format(number);
}
handleReset1 = () => {
this.setState({
productCategory:'',
productBrand:'',
productType:''
});
}
handleReset2 = () => {
this.setState({
productName:'',
productPrice:0,
productCode:'',
productDescription:''
});
}
render() {
const {step} = this.state;
const {productCategory, productBrand, productType, productName, productPrice, productCode, productDescription} = this.state;
const values = {productCategory, productBrand, productType, productName, productPrice, productCode, productDescription};
switch(step){
case 1:
return (
<FormProducts1 nextStep = {this.nextStep}
handleChange = {this.handleChange} handleReset1 = {this.handleReset1}
values={values}
/>
)
case 2:
return(
<FormProducts2 nextStep = {this.nextStep}
prevStep = {this.prevStep} handleChange = {this.handleChange}
handleReset2 = {this.handleReset2}
values={values} toCurrency = {this.toCurrency}
/>
)
case 3:
return(
<FormProducts3 nextStep = {this.nextStep} prevStep = {this.prevStep}
values={values} toCurrency = {this.toCurrency}
/>
)
case 4:
return(
<FormProductsSuccess/>
)
}
}
}
NewProduct.propTypes = {
value: PropTypes.string,
onChange: PropTypes.func
};
export default NewProduct;
Thanks!
It took two days to make it error-free... and I had hoped this would be work at your end..
Here, you can find react-bootstrap all input(form-control) along with formik and yup validation package
import React from 'react';
import { Container, Row, Col, Image, Form, Button } from 'react-bootstrap';
import style from '../styles/Contact.module.css';
import { Formik, Field} from 'formik';
import * as Yup from 'yup';
const dropdown=[
{
key:"Select an option",
value:""
},
{
key:"Option 1",
value:"option1"
},
{
key:"Option 2",
value:"option2"
},
{
key:"Option 3",
value:"option3"
}
]
// RegEx for phone number validation
const phoneRegExp = /^(\+?\d{0,4})?\s?-?\s?(\(?\d{3}\)?)\s?-?\s?(\(?\d{3}\)?)\s?-?\s?(\(?\d{4}\)?)?$/
// Schema for yup
const validationSchema = Yup.object().shape({
name: Yup.string()
.min(2, "*Names must have at least 2 characters")
.max(30, "*Names can't be longer than 30 characters")
.required("*Name is required"),
email: Yup.string()
.email("*Must be a valid email address")
.max(100, "*Email must be less than 100 characters")
.required("*Email is required"),
phone: Yup.string()
.min(10, "*Names can't be longer than 10 numbers")
.matches(phoneRegExp, "*Phone number is not valid")
.required("*Phone number required"),
msg: Yup.string()
.min(2, "*Messages must have at least 2 characters")
.max(250, "*Messages can't be longer than 250 characters")
.required("*Messages is required"),
selectionOption: Yup.string()
// .of(Yup.string())
// .min(1)
.required('Required'),
});
const Signup = () => {
return (
<>
<Formik
initialValues={{ name: "", email: "", phone: "", msg: "",selectionOption:"" }}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting, resetForm }) => {
// When button submits form and form is in the process of submitting, submit button is disabled
console.log(values)
setSubmitting(true);
// Simulate submitting to database, shows us values submitted, resets form
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
resetForm();
setSubmitting(false);
}, 500);
}}
>
{({ values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting }) => (
<Form className="form" onSubmit={handleSubmit} autoComplete="off" name="contact" method="POST" >
<Row className="mb-5">
<Col lg={6} md={6} sm={12}>
<Form.Group controlId="formName">
<Form.Label className="form_label" >FullName</Form.Label>
<Form.Control
type="text"
name="name"
placeholder="Full Name"
onChange={handleChange}
onBlur={handleBlur}
value={values.name}
className={touched.name && errors.name ? "has-error" : null}
/>
{touched.name && errors.name ? (
<div className="error-message">{errors.name}</div>
) : null}
</Form.Group>
</Col>
<Col lg={6} md={6} sm={12}>
<Form.Group>
<Form.Label className="form_label" >Number</Form.Label>
<Form.Control
type="text"
name="phone"
placeholder="Phone"
onChange={handleChange}
onBlur={handleBlur}
value={values.phone}
className={touched.phone && errors.phone ? "has-error" : null}
/>
{touched.phone && errors.phone ? (
<div className="error-message">{errors.phone}</div>
) : null}
</Form.Group>
</Col>
</Row>
<Row className="mb-5">
<Col lg={6} md={6} sm={12}>
<Form.Group>
<Form.Label className="form_label" >Email</Form.Label>
<Form.Control
type="text"
name="email"
placeholder="Email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
className={touched.email && errors.email ? "has-error" : null}
/>
{touched.email && errors.email ? (
<div className="error-message">{errors.email}</div>
) : null}
</Form.Group>
</Col>
<Col lg={6} md={6} sm={12}>
{/* <Form.Select aria-label="Default select example">
<option>Open this select menu</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</Form.Select> */}
<Form.Group controlId="exampleForm.ControlSelect1">
<Form.Label>Example select</Form.Label>
<Form.Control as="select" name ="selectionOption" onChange={handleChange}
onBlur={handleBlur} value={values.selectionOption}
className={touched.selectionOption && errors.selectionOption ? "has-error" : null}
>
{
dropdown.map(drop=>{return(
<option key={drop.value} value={drop.value}>
{drop.key}
</option>
)
}
)
}
</Form.Control>
{/* <ErrorMessage name="selectionOption"></ErrorMessage> */}
{touched.selectionOption && errors.selectionOption ? (
<div className="error-message">{errors.selectionOption}</div>
) : null}
{/* <Form.Label>Example select</Form.Label>
<Field as="select" name ="selectionOption" onChange={handleChange}
onBlur={handleBlur} value={values.selectionOption}
style={{ display: "block" }}
// isValid={touched.selectionOption && !errors.selectionOption}
// isInvalid={!errors.selectionOption}
className={touched.selectionOption && errors.selectionOption ? "has-error" : null}
>
<option className='text-muted'>---</option>
<option value="ortho">ortho</option>
<option value="pedri">pedri</option>
<option value="crown">crown</option>
</Field>
{touched.selectionOption && errors.selectionOption ? (
<div className="error-message">{errors.selectionOption}</div>
) : null} */}
</Form.Group>
</Col>
</Row>
<Row className="mb-5">
<Col lg={12} md={12} sm={12}>
<Form.Group controlId="formmsg">
<Form.Label>Messages :</Form.Label>
<Form.Control
type="text"
name="msg"
as="textarea" rows={4}
placeholder="Query / Feedback"
onChange={handleChange}
onBlur={handleBlur}
value={values.msg}
className={touched.msg && errors.msg ? "has-error" : null}
/>
{touched.msg && errors.msg ? (
<div className="error-message">{errors.msg}</div>
) : null}
</Form.Group>
</Col>
</Row>
<Button type="submit" className={style.customizeBtn} name="contact" id="contact" disabled={isSubmitting}>
<Image src="img/send.png" className={style.imgBtn} />
<span className={style.titleBtn}>Submit</span>
</Button>
</Form>
)}
</Formik>
</>
)
}
export default Signup;
Related
Problem
I'm working on project using reactjs and graphql with apollo client. I'm trying to get user by id and it works, the value shows in the form modal.
But, I can't delete or add the text or content inside form input.
I'm using onChange handler but it doesn't work. here's the code
import React, { useEffect, useState } from "react";
import { useMutation, useQuery } from "#apollo/client";
import { Button, Modal, Form } from "react-bootstrap";
import { GET_USER_BY_ID } from "../../../gql/query";
const ModalEdit = (props) => {
// state for check input component
const [isChecked, setIsChecked] = useState('ACTIVE');
const [value, setValue] = useState({
full_name: "",
email: "",
phone: "",
address: "",
password: "",
group_id: "",
});
useEffect(() => {
if (props.show) {
document.body.classList.add("modal-open");
}
return () => {
if (document.body.classList.contains("modal-open")) {
document.body.classList.remove("modal-open");
}
};
}, [props.show]);
const { data, loading, error } = useQuery(GET_USER_BY_ID, {
variables: { username: props.username },
});
const dataUser = data?.getUserByID;
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
const onChange = (event) => {
setValue({
...value,
[event.target.name]: event.target.value
})
}
return (
<Modal show={props.show}>
<Modal.Header>
<Modal.Title> <span>FORMULIR AKUN PENGGUNA</span> </Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group className="mb-3">
<Form.Label>Role Akun</Form.Label>
<Form.Select aria-label="pilih user role">
<option>{dataUser.group_id}</option>
<option>Admin RJ</option>
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Nama Lengkap</Form.Label>
<Form.Control name="full_name" value={dataUser.full_name} onChange= {onChange} />
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Email</Form.Label>
<Form.Control type="email" name="email" value={dataUser.email} onChange={ onChange }/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Phone</Form.Label>
<Form.Control type="text" name="phone" value={dataUser.phone} onChange={ onChange } />
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Address</Form.Label>
<Form.Control type="text" name="address" value={dataUser.address} onChange={ onChange } />
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Password</Form.Label>
<Form.Control type="password" name="password" value={dataUser.password} onChange={ onChange } />
</Form.Group>
<Form.Label>Aktifkan Akun</Form.Label>
{dataUser.status === 'ACTIVE' ? (
<Form.Check
type="switch"
checked={isChecked}
onChange={(event) => setIsChecked(event.target.checked)}
id="custom-switch"
label="Aktifkan Akun"
/> ) : (
<Form.Check
type="switch"
id="custom-switch"
checked={isChecked}
onChange={(event) => setIsChecked(event.target.checked)}
label="Aktifkan Akun"
/>
)}
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="primary" type="submit" >Submit</Button>
<Button variant="secondary" onClick={props.onClose}>
Close
</Button>
</Modal.Footer>
</Modal>
);
};
export default ModalEdit;
Question
How can i'm edit the form input?
Any help will be apprieciated, thank you
The value prop of your inputs are causing this issue. Instead of binding the value directly to dataUser.address and such, you should set them to value.address and so on.
As for showing the user's data, you should map the values when creating the state.
Your state should be as follows:
const [value, setValue] = useState({
full_name: dataUser.full_name,
email: dataUser.email,
phone: dataUser.phone
address: dataUser.address,
password: dataUser.password,
group_id: dataUser.group_id,
});
And your input should be as follows:
<Form.Control name="full_name" value={value.full_name} onChange= {onChange} />
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.
Using one Single stateless Datepicker component present in datePicker.js which I am repurposing as hire date & exit date how can i add a validation from date can not be greater than Thru date before i submit the form (EmployeeForm.js?
I have added my codes for DatePicker component where I have added some props to it and KeyboardDatepicker is from Material UI. I have also added my form details where in I am using this Datepicker component.
DatePicker.js
import React from 'react'
import { MuiPickersUtilsProvider, KeyboardDatePicker } from "#material-ui/pickers";
import DateFnsUtils from "#date-io/date-fns";
import { FormControl, InputLabel, Select as MuiSelect, FormHelperText } from '#material-ui/core';
export default function DatePicker(props) {
const { name, label, value, error=null,onChange } = props
const convertToDefEventPara = (name, value) => ({
target: {
name, value
}
})
return (
<FormControl variant="outlined"
{...(error && {error:true})}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker disableToolbar variant="inline" inputVariant="outlined"
label={label}
format="MM/dd/yyyy"
name={name}
value={value}
onChange={date =>onChange(convertToDefEventPara(name,date))}
// minimumdate={value}
selectsStart
InputLabelProps={{ style: { fontSize: 17 } }}
/>
</MuiPickersUtilsProvider>
{error && <FormHelperText>{error}</FormHelperText>}
</FormControl>
)
}
Employee.Js
import React, { useState, useEffect } from 'react'
import { Grid, } from '#material-ui/core';
import Controls from "../../components/controls/Controls";
import { useForm, Form } from '../../components/useForm';
import * as employeeService from "../../services/employeeService";
const genderItems = [
{ id: 'male', title: 'Male' },
{ id: 'female', title: 'Female' },
{ id: 'other', title: 'Other' },
]
const initialFValues = {
id: 0,
fullName: '',
email: '',
mobile: '',
city: '',
gender: 'male',
departmentId: '',
hireDate: new Date(),
exitDate: new Date(),
isPermanent: false,
}
export default function EmployeeForm(props) {
const { addOrEdit, recordForEdit } = props
const validate = (fieldValues = values) => {
let temp = { ...errors }
if ('fullName' in fieldValues)
temp.fullName = fieldValues.fullName ? "" : "This field is required."
if ('email' in fieldValues)
temp.email = (/$^|.+#.+..+/).test(fieldValues.email) ? "" : "Email is not valid."
if ('mobile' in fieldValues)
temp.mobile = fieldValues.mobile.length > 9 ? "" : "Minimum 10 numbers required."
if ('departmentId' in fieldValues)
temp.departmentId = fieldValues.departmentId.length != 0 ? "" : "This field is required."
setErrors({
...temp
})
if (fieldValues == values)
return Object.values(temp).every(x => x == "")
}
const {
values,
setValues,
errors,
setErrors,
handleInputChange,
resetForm
} = useForm(initialFValues, true, validate);
const handleSubmit = e => {
e.preventDefault()
if (validate()) {
addOrEdit(values, resetForm);
}
}
useEffect(() => {
if (recordForEdit != null)
setValues({
...recordForEdit
})
}, [recordForEdit])
return (
<Form onSubmit={handleSubmit}>
<Grid container>
<Grid item xs={6}>
<Controls.Input
name="fullName"
label="Full Name"
value={values.fullName}
onChange={handleInputChange}
error={errors.fullName}
/>
<Controls.Input
label="Email"
name="email"
value={values.email}
onChange={handleInputChange}
error={errors.email}
/>
<Controls.Input
label="Mobile"
name="mobile"
value={values.mobile}
onChange={handleInputChange}
error={errors.mobile}
/>
<Controls.Input
label="City"
name="city"
value={values.city}
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={6}>
<Controls.RadioGroup
name="gender"
label="Gender"
value={values.gender}
onChange={handleInputChange}
items={genderItems}
/>
<Controls.Select
name="departmentId"
label="Department"
value={values.departmentId}
onChange={handleInputChange}
options={employeeService.getDepartmentCollection()}
error={errors.departmentId}
/>
<Controls.DatePicker
name="hireDate"
label="Date of Hire "
value={values.hireDate}
onChange={handleInputChange}
/>
<Controls.DatePicker
name="exitDate"
label="Date of Exit"
value={values.exitDate}
onChange={handleInputChange}
/>
<Controls.Checkbox
name="isPermanent"
label="Permanent Employee"
value={values.isPermanent}
onChange={handleInputChange}
/>
<div>
<Controls.Button
type="submit"
text="Submit" />
<Controls.Button
text="Reset"
color="default"
onClick={resetForm} />
</div>
</Grid>
</Grid>
</Form>
)
}
When I'm launching my project, I was faced with the error:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
App.js
import React from "react";
import {
Formik,
Field,
Form,
useField,
FieldAttributes,
FieldArray
} from "formik";
import {
TextField,
Button,
Checkbox,
Radio,
FormControlLabel,
Select,
MenuItem
} from "#material-ui/core";
import * as yup from "yup";
type MyRadioProps = { label: string } & FieldAttributes<{}>;
const MyRadio: React.FC<MyRadioProps> = ({ label, ...props }) => {
const [field] = useField<{}>(props);
return <FormControlLabel {...field} control={<Radio />} label={label} />;
};
const MyTextField: React.FC<FieldAttributes<{}>> = ({
placeholder,
...props
}) => {
const [field, meta] = useField<{}>(props);
const errorText = meta.error && meta.touched ? meta.error : "";
return (
<TextField
placeholder={placeholder}
{...field}
helperText={errorText}
error={!!errorText}
/>
);
};
const validationSchema = yup.object({
firstName: yup
.string()
.required()
.max(10),
pets: yup.array().of(
yup.object({
name: yup.string().required()
})
)
});
const App: React.FC = () => {
return (
<div>
<Formik
validateOnChange={true}
initialValues={{
firstName: "",
lastName: "",
isTall: false,
cookies: [],
yogurt: "",
pets: [{ type: "cat", name: "jarvis", id: "" + Math.random() }]
}}
validationSchema={validationSchema}
// validate={values => {
// const errors: Record<string, string> = {};
// if (values.firstName.includes("bob")) {
// errors.firstName = "no bob";
// }
// return errors;
// }}
onSubmit={(data, { setSubmitting }) => {
setSubmitting(true);
// make async call
console.log("submit: ", data);
setSubmitting(false);
}}
>
{({ values, errors, isSubmitting }) => (
<Form>
<MyTextField placeholder="first name" name="firstName" />
<div>
<Field
placeholder="last name"
name="lastName"
type="input"
as={TextField}
/>
</div>
<Field name="isTall" type="checkbox" as={Checkbox} />
<div>cookies:</div>
<Field
name="cookies"
type="checkbox"
value="chocolate chip"
as={Checkbox}
/>
<Field
name="cookies"
type="checkbox"
value="snickerdoodle"
as={Checkbox}
/>
<Field name="cookies" type="checkbox" value="sugar" as={Checkbox} />
<div>yogurt</div>
<MyRadio name="yogurt" type="radio" value="peach" label="peach" />
<MyRadio
name="yogurt"
type="radio"
value="blueberry"
label="blueberry"
/>
<MyRadio name="yogurt" type="radio" value="apple" label="apple" />
<FieldArray name="pets">
{arrayHelpers => (
<div>
<Button
onClick={() =>
arrayHelpers.push({
type: "frog",
name: "",
id: "" + Math.random()
})
}
>
add pet
</Button>
{values.pets.map((pet, index) => {
return (
<div key={pet.id}>
<MyTextField
placeholder="pet name"
name={`pets.${index}.name`}
/>
<Field
name={`pets.${index}.type`}
type="select"
as={Select}
>
<MenuItem value="cat">cat</MenuItem>
<MenuItem value="dog">dog</MenuItem>
<MenuItem value="frog">frog</MenuItem>
</Field>
<Button onClick={() => arrayHelpers.remove(index)}>
x
</Button>
</div>
);
})}
</div>
)}
</FieldArray>
<div>
<Button disabled={isSubmitting} type="submit">
submit
</Button>
</div>
<pre>{JSON.stringify(values, null, 2)}</pre>
<pre>{JSON.stringify(errors, null, 2)}</pre>
</Form>
)}
</Formik>
</div>
);
};
export default App;
index.tsx
import React, {Component} from 'react';
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
I have googled it and don't find an appropriate answer
I have a special TextField wrapper for a Material-UI field that seems to be working well. For some reason when I use native Material UI fields, they do not update the form data upon submission.
Here is the relevant code (but I've added a code sandbox at the end of the post to help demonstrate the issue)
TestForm.tsx
import { Card, CardActions, CardContent } from "#material-ui/core";
import { Form, Formik, Field } from "formik";
import React from "react";
import SubmitButton from "../../SubmitButton";
import CancelButton from "../../CancelButton";
import TextField from "../../TextField";
import MuiTextField from "#material-ui/core/TextField";
interface TestFormProps {
onSave: (fields: FormFields) => void;
onCancel: () => void;
}
export interface FormFields {
textField: string;
numericField: number;
materialField: string;
materialFieldFromComponent: string;
}
const initialValues: FormFields = {
textField: "Default Text",
numericField: 42,
materialField: "Default Mui",
materialFieldFromComponent: "Default Mui from Component"
};
const TestForm: React.FC<TestFormProps> = ({ onCancel, onSave }) => {
return (
<Formik
initialValues={initialValues}
onSubmit={async (fields, { setSubmitting }) => {
setSubmitting(true);
onSave(fields);
}}
>
{({ isSubmitting }) => (
<Form>
<Card>
<CardContent>
<TextField name="textField" label="Text field" />
<TextField
name="numericField"
type="number"
label="Numeric field"
/>
<div>
<MuiTextField
name="materialField"
label="Mui field"
variant="outlined"
margin="dense"
/>
</div>
<Field
name="materialFieldFromComponent"
label="Mui field from Component"
variant="outlined"
margin="dense"
component={MuiTextField}
/>
</CardContent>
<CardActions>
<SubmitButton disabled={isSubmitting} />
<CancelButton onCancel={onCancel} />
</CardActions>
</Card>
</Form>
)}
</Formik>
);
};
export default TestForm;
TextField.tsx
import React from "react";
import { FieldAttributes, useField } from "formik";
import {
InputBaseComponentProps,
TextField as MuiTextField
} from "#material-ui/core";
interface TextFieldProps {
label: string;
inline?: boolean;
inputProps?: InputBaseComponentProps;
}
const Field: React.FC<FieldAttributes<TextFieldProps>> = (props) => {
const { type, label, placeholder, inputProps } = props;
const [field, meta] = useField<TextFieldProps>(props);
const errorText = meta.error && meta.touched ? meta.error : "";
return (
<MuiTextField
{...field}
label={label}
variant="outlined"
margin="dense"
type={type}
placeholder={placeholder ? placeholder : label}
helperText={errorText}
error={!!errorText}
inputProps={inputProps}
/>
);
};
const TextField: React.FC<FieldAttributes<TextFieldProps>> = ({
inline = false,
...props
}) => {
return inline ? (
<Field {...props} />
) : (
<div>
<Field {...props} />
</div>
);
};
export default TextField;
Here's the code sandbox:
https://codesandbox.io/s/recursing-northcutt-5deen
This is because the TextField that you have created passes the correct field properties, while the other direct implementation of the material UI fields do not.
Passing the correct properties as follow, will do the trick:
{({ isSubmitting, handleChange, handleBlur, values }) => (
...
<div>
<MuiTextField
name="materialField"
label="Mui field"
variant="outlined"
margin="dense"
onChange={handleChange}
onBlur={handleBlur}
value={values.materialField}
/>
</div>
<Field
name="materialFieldFromComponent"
label="Mui field from Component"
variant="outlined"
margin="dense"
render={({ field }) => <MuiTextField {...field} />}
/>
...
The MuiTextField now has onChange and values passed on its properties, thus updating the values correctly.
Complete code: TestForm.tsx
import { Card, CardActions, CardContent } from "#material-ui/core";
import { Form, Formik, Field } from "formik";
import React from "react";
import SubmitButton from "../../SubmitButton";
import CancelButton from "../../CancelButton";
import TextField from "../../TextField";
import MuiTextField from "#material-ui/core/TextField";
interface TestFormProps {
onSave: (fields: FormFields) => void;
onCancel: () => void;
}
export interface FormFields {
textField: string;
numericField: number;
materialField: string;
materialFieldFromComponent: string;
}
const initialValues: FormFields = {
textField: "Default Text",
numericField: 42,
materialField: "Default Mui",
materialFieldFromComponent: "Default Mui from Component"
};
const TestForm: React.FC<TestFormProps> = ({ onCancel, onSave }) => {
return (
<Formik
initialValues={initialValues}
onSubmit={async (fields, { setSubmitting }) => {
setSubmitting(true);
onSave(fields);
}}
>
{({ isSubmitting, handleChange, handleBlur, values }) => (
<Form>
<Card>
<CardContent>
<TextField name="textField" label="Text field" />
<TextField
name="numericField"
type="number"
label="Numeric field"
/>
<div>
<MuiTextField
name="materialField"
label="Mui field"
variant="outlined"
margin="dense"
onChange={handleChange}
onBlur={handleBlur}
value={values.materialField}
/>
</div>
<Field
name="materialFieldFromComponent"
label="Mui field from Component"
variant="outlined"
margin="dense"
render={({ field }) => <MuiTextField {...field} />}
/>
</CardContent>
<CardActions>
<SubmitButton disabled={isSubmitting} />
<CancelButton onCancel={onCancel} />
</CardActions>
</Card>
</Form>
)}
</Formik>
);
};
export default TestForm;