I using material-ui with react.
I want to do validations such as require and maxlength and minlength. according to material-ui docs I have to use the error prop and helperText to display the error. which mean I have to trigger a function myself that check the control and display the error. :(
I wounder if this is the right way to handle validation with react, the Textfield itself can't display require message (for example)? I have to specify each error myself? for example in angular I just add require or minlength and the control display the correct error.
Or maybe there is an easy way to do it?
got it :) here my ex :
import { Link } from 'react-router-dom';
import useForm from "react-hook-form";
import * as yup from 'yup';
const LoginFormSchema = yup.object().shape({
password: yup.string().required().min(4),
username: yup.string().required().min(4)
});
export function LoginForm(props) {
const { register, handleSubmit, watch, errors } = useForm({ defaultValues, validationSchema: LoginFormSchema });
const onSubmit = data => { props.onSubmit(data); }
<div className="form-container">
<form className="form" onSubmit={handleSubmit(onSubmit)}>
<div className="form-header">
<i className="material-icons">
account_circle
</i>
<h2>Login Form</h2>
</div>
<TextField name="username" label="username" inputRef={register} />
<span className="error-message">
{errors.username && errors.username.type === "required" && "username is required"}
{errors.username && errors.username.type === "min" && "username required to be more than 4 characters"}
</span>
<TextField type="password" name="password" label="password" inputRef={register} />
<span className="error-message">
{errors.password && errors.password.type === "required" && "password is required"}
{errors.password && errors.password.type === "min" && "password required to be more than 4 characters"}
</span>
</form>
You need to install yup and formik: npm i -s yup formik
Here is a working sample with formik yup and material-ui:
import React from "react";
import { Formik, Form, useField } from "formik";
import { TextField } from "#material-ui/core";
import * as yup from "yup";
//Reusable Textbox
const MyTextField = ({
placeholder,
type = "normal",
...props
}) => {
const [field, meta] = useField<{}>(props);
const errorText = meta.error && meta.touched ? meta.error : "";
return (
<TextField
variant="outlined"
margin="normal"
type={type}
placeholder={placeholder}
{...field}
helperText={errorText}
error={!!errorText}
/>
);
};
const validationSchema = yup.object({
username: yup
.string()
.required()
.max(30)
.min(2)
.label("Username"),
password: yup
.string()
.required()
.max(30)
.min(2)
.label("Password")
});
const Signin = ({ history }) => {
return (
<div className="SignupOuter">
<Formik
validateOnChange={true}
initialValues={{
username: "",
password: "",
loading: false
}}
validationSchema={validationSchema}
onSubmit={async (data1, { setSubmitting }) => {
setSubmitting(true);
//Call API here
}}
>
{({ values, errors, isSubmitting }) => (
<Form className="Signup">
<MyTextField placeholder="Username" name="username" />
<MyTextField
placeholder="Password"
name="password"
type="password"
/>
</Form>
)}
</Formik>
</div>
);
};
export default Signin;
Related
I'm currently in the process of creating a simple form using MUI TextFields, Formik and some Yup validation.
I ran into some issues with performance with lower spec devices when scrolling and I have extracted stuff like MUI styles and other components such as toastify notifications.
I am relatively new to react, but I have used MUI + Formik + Yup before with a much more complex form without any lag when I scroll and I now can't seem to fix it. The documentation for MUI has also been extremely confusing with many different versions, depracated use cases and so on. I figured useMemo() could work in this instance as there is sure to be some excessive re-rendering, but I am not sure how to implement it.
I'll gladly give more info if needed.
import React from "react";
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import {emailSuccess, emailError, emailSubmitting} from "../lib/toasts";
import {useSubmit} from "../lib/useSubmit";
import {postJson} from "../lib/http";
import {useStyles} from "../lib/inputStyle";
import {TextField} from "#mui/material";
import SendRoundedIcon from "#mui/icons-material/SendRounded";
import { useFormik } from "formik";
import * as Yup from "yup";
export default function Contact () {
const toastId = React.useRef(null);
const classes = useStyles();
const formik = useFormik({
initialValues: {
name: '',
email: '',
message: ''
},
validationSchema: Yup.object({
name: Yup
.string()
.max(255)
.required(
'Name is required'),
email: Yup
.string()
.email(
'Must be a valid email')
.max(255)
.required(
'E-mail address is required'),
message: Yup
.string()
.max(1200)
.required(
'You cannot send an empty message')
}),
onSubmit: () => {
//This should be optional... Please fix!
}
});
const { handleSubmit: handleEmail, submitting, error } = useSubmit(
async () => {
emailSubmitting(toastId);
await postJson("/api/mail", {
name: formik.values.name,
email: formik.values.email,
message: formik.values.message,
});
},
() => {
emailSuccess(toastId);
},
);
if (error) {
emailError(toastId)
}
return (
<div className="contact">
<div className="section">
<div className="contact-innerRef"/>
<h4>Contact</h4>
<div className="contact-card">
<h5>Get In Touch</h5>
<hr/>
<form className="contact-form" action="" method="post" encType="text/plain">
<TextField className={classes.root}
error={Boolean(formik.touched.name && formik.errors.name)}
helperText={formik.touched.name && formik.errors.name}
label="Name"
margin="normal"
name="name"
onChange={formik.handleChange}
value={formik.values.name}
variant="outlined"
/>
<TextField className={classes.root}
error={Boolean(formik.touched.email && formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
label="Email"
margin="normal"
name="email"
onChange={formik.handleChange}
value={formik.values.email}
variant="outlined"
/>
<TextField className={classes.root}
error={Boolean(formik.touched.message && formik.errors.message)}
helperText={formik.touched.message && formik.errors.message}
label="Message"
margin="normal"
name="message"
onChange={formik.handleChange}
value={formik.values.message}
variant="outlined"
multiline
rows={7}
/>
</form>
<button onClick={handleEmail} disabled={!formik.isValid || !formik.dirty || submitting}
className="btn">
<SendRoundedIcon height="50%"/>
</button>
<ToastContainer />
</div>
</div>
</div>
);
}```
I am new to React and formik and struggling to get my radio buttons to display in the browser. I think it is because the radio buttons are not defined as props in my formikcontrol function. How do I add it to the other props in my formikcontrol function? Please advise how to solve this issue. Thanks in advance
App.js:
import React from 'react';
import './App.css';
import FormikContainer from './components/FormikContainer';
import LoginForm from './components/LoginForm';
import Registrationform from './components/RegistrationForm';
function App() {
return (
<div>
<LoginForm />
<Registrationform />
</div>
);
}
export default App;
RegistrationForm:
import React from 'react';
import { Formik, Form } from 'formik';
import * as yup from 'yup';
import FormikControl from './FormikControl';
function Registrationform() {
const options = [
{ key: 'Email', value: 'emailmoc' },
{ key: 'Telephone', vlaue: 'telephonemoc' }
];
const initialValues = {
email: '',
password: '',
confirmPassword: '',
modeOfContact: '',
phone: ''
};
const validationSchema = yup.object({
email: yup.string().email('Invalid email format').required('Required'),
password: yup.string().required('Required'),
confirmPassword: yup
.string()
.oneOf([yup.ref('password'), ''], 'Passwords must match')
.required('required'),
modeOfContact: yup.string().required('Required'),
phone: yup.string().when('modeOfContact', {
is: 'telephonemoc',
then: yup.string().required('Required')
})
});
const onSubmit = (values) => {
console.log('Form data', values);
};
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
{(formik) => {
return (
<Form>
<FormikControl
control="input"
type="email"
label="Email"
name="email"
/>
<FormikControl
control="input"
type="password"
label="Password"
name="password"
/>
<FormikControl
control="input"
type="password"
label="Confirm Password"
name="confirmPassword"
/>
<FormikControl
control="radio"
type="Mode of contact"
label="modeOfContact"
options={options}
/>
<FormikControl
control="input"
type="text"
label="Phone number"
name="phone"
/>
<button type="submit" disabled={!formik.isValid}>
Submit
</button>
</Form>
);
}}
</Formik>
);
}
export default Registrationform;
FormikControl:
import React from 'react';
function FormikControl({ control, id, label, ...rest }) {
return (
<>
{control === 'input' && <label htmlFor={id}>{label}</label>}
<input id={id} {...rest} />
</>
);
}
export default FormikControl;
FormikContainer:
import React from 'react';
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import FormikControl from './FormikControl';
function FormikContainer() {
const initialValues = {
email: '',
password: ''
};
const validationschema = Yup.object({
email: Yup.string().required('Required')
});
const onSubmit = (values) => console.log('Form data', values);
return (
<div>
<Formik
initialValues={initialValues}
validationschema={validationschema}
onSubmit={onSubmit}
>
{(formik) => (
<Form>
<FormikControl
control="input"
type="email"
label="Email"
name="email"
/>
<FormikControl
control="input"
type="password"
label="Password"
name="password"
/>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
}
export default FormikContainer;
In order to get radio button rendered using Formik, you have to import Field component and use it by sending type="radio", here is how it should look like:
enter link description here
Or by using the same component you created but with type="radio" :
<FormikControl
control="input"
type="radio"
.....
/>
I using EXTReact and have TextField
Here is my code.
<Textfield
required
label="Required Field"
requiredMessage="This field is required."
errorTarget="under"
name="field"
onChange={(e) => handleChaneValue(e)}
/>
<Button
ui="confirm"
text="BTN submit"
handler={handleClick}
style={{border: '1px solid black'}}
/>
When the submit Button is clicked, if the field does not have any value it should show the error message.
You should:
- step 1 : When textfField change, update the state of your component (with the value of textfield) ==> Two ways binding will be necessary (Data binding in React)
- step 2 : Then, you create your error message div/text/paragraph anyway (where you want and with the style you need), and you add style display:none. Here you just set the html and css of the feature.
- step 3 : Then, on click (button), you check the state value of textField (the one you made at step 1). If it's empty, you change style of the error message div ==> display:block
Please refer to the following code.
Note: The code is only looking for a single TextField. I have used material-ui TextField and Button component(I am not sure which one you're using). However, the logic should remain the same.
import React, { useState } from 'react';
import TextField from '#mui/material/TextField';
import Button from '#mui/material/Button';
function App() {
const [field, setField] = useState('');
const [error, setError] = useState(false);
const handleClick = () => {
if (!field) {
setError(true);
return null;
}
};
const handleChangeValue = (e) => {
setField(e.target.value);
};
return (
<div>
<TextField
required
label="Required Field"
value={field}
error={!!error}
name="field"
onChange={(e) => handleChangeValue(e)}
helperText={error ? 'this is required' : ''}
/>
<Button onClick={handleClick} style={{ border: '1px solid black' }}>
Submit
</Button>
</div>
);
}
export default App;
You can use Formik Formik-official.
import React from 'react';
import ReactDOM from 'react-dom';
import { useFormik } from 'formik';
import * as yup from 'yup';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
const validationSchema = yup.object({
email: yup
.string('Enter your email')
.email('Enter a valid email')
.required('Email is required'),
password: yup
.string('Enter your password')
.min(8, 'Password should be of minimum 8 characters length')
.required('Password is required'),
});
const WithMaterialUI = () => {
const formik = useFormik({
initialValues: {
email: 'foobar#example.com',
password: 'foobar',
},
validationSchema: validationSchema,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<div>
<form onSubmit={formik.handleSubmit}>
<TextField
fullWidth
id="email"
name="email"
label="Email"
value={formik.values.email}
onChange={formik.handleChange}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
<TextField
fullWidth
id="password"
name="password"
label="Password"
type="password"
value={formik.values.password}
onChange={formik.handleChange}
error={formik.touched.password && Boolean(formik.errors.password)}
helperText={formik.touched.password && formik.errors.password}
/>
<Button color="primary" variant="contained" fullWidth type="submit">
Submit
</Button>
</form>
</div>
);
};
ReactDOM.render(<WithMaterialUI />, document.getElementById('root'));
I have a value in a form (email) that I want to transform into lowercase. I have a transform in Yup that's working, but Formik does not show a lowercase value.
How do I make it so that, when I enter the email in uppercase, it gets converted to lowercase automatically?
Here is my code:
import React from "react";
import { render } from "react-dom";
import { Formik } from "formik";
import * as Yup from "yup";
import { DisplayFormikState } from "./helper";
import "./style.css";
const validationSchema = Yup.object({
email: Yup.string()
.transform(function (value, originalvalue) {
return this.isType(value) && value !== null ? value.toLowerCase() : value;
})
.email()
.required()
.label("Email")
});
const App = () => (
<div className="app">
<Formik
initialValues={{ name: "" }}
onSubmit={() => {}}
validationSchema={validationSchema}
>
{(props) => {
const { handleBlur, handleChange, values, errors, touched } = props;
return (
<form onSubmit={props.handleSubmit}>
<h1>Email Form</h1>
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{errors.email && touched.email && errors.email}
<button type="submit">Submit</button>
<DisplayFormikState {...props} />
</form>
);
}}
</Formik>
</div>
);
render(<App />, document.getElementById("root"));
In your form, Formik and Yup essentially have two separate duties:
Formik manages the form's state (values)
Yup performs validations and tells Formik whether the values are valid or not
Yup will not set the values in your form.
Remove the case conversion from the Yup transformation. Instead, simply set the value to lowercase and pass it to setFieldValue() before yup does the validation.
const validationSchema = Yup.object({
email: Yup.string()
.email()
.required()
.label("Email")
});
const App = () => (
<div className="app">
<Formik
initialValues={{ name: "" }}
onSubmit={() => {}}
validationSchema={validationSchema}
>
{(props) => {
const { handleBlur, setFieldValue, values, errors, touched } = props;
return (
<form onSubmit={props.handleSubmit}>
<h1>Email Form</h1>
<input
type="email"
name="email"
onChange={(e)=>{
const value = e.target.value || "";
setFieldValue('email', value.toLowerCase());
}}
onBlur={handleBlur}
value={values.email}
/>
{errors.email && touched.email && errors.email}
<button type="submit">Submit</button>
<DisplayFormikState {...props} />
</form>
);
}}
</Formik>
</div>
);
render(<App />, document.getElementById("root"));
I have a customer form which takes two values.
Customer name
Customer Emails (Which can be multiples)
I've given an add button besides the email field through which user can add more emails to the form. now i want to validate each email that's been added. also if it's added it is required too. empty email is not allowed.
The question is i have only one validation schema to validate email field. how can i use the same field to validate multiple emails?
Even after adding the correct email. it still gives error !
Please see the sandbox link to see the code. code sandbox link
Here is the working code with multiple email validation and errors.
I've used Formik FieldArray to handle multiple emails.
You can replace your code with this in your sandbox to test.
import React from "react";
import { TextField, IconButton } from "#material-ui/core";
import {
AddCircleOutlined as AddCircleOutlinedIcon,
IndeterminateCheckBox as IndeterminateCheckBoxIcon
} from "#material-ui/icons";
//FORMIK
import { Formik, FieldArray, getIn, ErrorMessage } from "formik";
import * as Yup from "yup";
export default function UnitsDrawer(props) {
const callAPI = e => {
console.log(e.name);
console.log(e.email);
};
const testSchema = Yup.object().shape({
name: Yup.string().required("Customer name is required"),
email: Yup.array().of(
Yup.string()
.email("Enter a valid email")
.required("Email is required")
)
});
const initialValues = {
name: "",
email: [""]
};
const formRef = React.useRef();
return (
<div>
<Formik
innerRef={formRef}
validationSchema={testSchema}
initialValues={initialValues}
onSubmit={(values, actions) => {
actions.setSubmitting(false);
callAPI(values);
}}
>
{({
handleChange,
handleBlur,
values,
errors,
touched,
handleSubmit,
isSubmitting,
}) => {
return (
<>
<div>
<TextField
label="Customer Name"
name="name"
margin="normal"
variant="outlined"
error={errors.name && touched.name}
onChange={handleChange}
onBlur={handleBlur}
value={values.name}
fullWidth
/>
<ErrorMessage name="name" component="div" />
</div>
<FieldArray name="email">
{({ push, remove }) =>
values.email.map((field, index) => (
<div key={`${index}`} className="dynamic-fields">
<div>
<TextField
label="Email"
variant="outlined"
className="input-item"
error={
getIn(touched, `email.${index}`) &&
getIn(errors, `email.${index}`)
}
name={`email.${index}`}
value={values.email[index]}
onChange={handleChange}
onBlur={handleBlur}
fullWidth
/>
<ErrorMessage name={`email.${index}`} component="div" />
</div>
<IconButton
aria-label="filter list"
className="add-icon"
onClick={() => {
push("");
}}
>
<AddCircleOutlinedIcon color="primary" />
</IconButton>
{values.email.length > 1 && (
<IconButton
aria-label="filter list"
className="add-icon"
onClick={() => {
remove(index);
}}
>
<IndeterminateCheckBoxIcon />
</IconButton>
)}
</div>
))
}
</FieldArray>
</>
);
}}
</Formik>
</div>
);
}