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>
);
}```
Related
Hi I am trying to combine React hook form, material UI, Yup validation but I just cant make it work, RHF and MUI works together but not with yup and RHF and YUP works but not with MUI.
anyway here is my work until now the only thing that is not working is that, the handleSubmit never fires and when I type something it gives me a uncontrolled error
import axios from "axios";
import styles from "../../styles/RegisterPage.module.css";
import TextField from "#mui/material/TextField";
import { Container } from "#mui/material";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import * as yup from "yup";
const schema = yup.object({
age: yup
.number()
.positive()
.integer()
.typeError("Amount must be a number")
.required(),
});
const MedicalHistoryPage = () => {
const {
handleSubmit,
control,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
const onSubmit = (data)=>{
console.log(data)
}
return (
<>
<Container maxWidth="md" className={styles.container}>
<form
onSubmit={handleSubmit(onSubmit)}
>
<div className={styles.title}>Ενημέρωσε το ιατρικό ιστορικό σου</div>
<div className={styles.form}>
<div className={styles.formsContainer}>
<div className={styles.input}>
<Controller
control={control}
name="age"
render={({ field }) => (
<TextField
{...field}
fullWidth
type="Number"
label="Input your age"
variant="outlined"
defaultValue=""
helperText={errors.age?.message}
error={!!errors.age?.message}
/>
)}
/>
</div>
</div>
<div className={styles.buttonsContainer}>
<div className={styles.loginButton}>
<input type="submit" className={styles.buttonStyle} />
</div>
</div>
</div>
</form>
</Container>
</>
);
};
export default MedicalHistoryPage;
Thanks for your time
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 really stuck up. I have a problem with my validation scheme. When I clicking on password field it shows an error also in the new password field. Validation scheme looks like right, but maybe I have missed something. So, this is my form:
And when I focusing field with current password error also is appearing inside the new password field. This is my code:
import React, { useState } from "react";
import { useFormik } from "formik";
import { useSelector } from "react-redux";
import { UserPasswordChangeSchema } from "common/types/user/userPasswordChangeSchema.type";
import { InputText, Button } from "../../components/common/index";
import { passChange } from "helpers/passChange";
import './SecurityPage.css'
import { Redirect } from "react-router-dom";
export const SecurityPage: React.FC = () => {
const email = useSelector((state: any) => state.userprofile.profile ? state.userprofile.profile.email : null)
const [makeRedirect, setMakeRedirect] = useState<boolean>(false);
const formik = useFormik(
{
initialValues: {
oldPassword: '',
password: '',
passwordConfirm: ''
},
onSubmit: async values => {
if (
await passChange({
email,
oldPassword: values.oldPassword,
newPassword: values.passwordConfirm
})
) {
setMakeRedirect(true);
}
},
validationSchema: UserPasswordChangeSchema
}
)
if (makeRedirect) {
return <Redirect to='/' />;
}
console.log(formik.touched)
return (
<div className="security">
<div className="security-info">
<h1 className="security-info-header">Security</h1>
<h2 className="security-info-subheader">Here you can change your password.</h2>
</div>
<form action="" className="security-form" onSubmit={formik.handleSubmit}>
<InputText
name={"Current password"}
propName={"oldPassword"}
value={formik.values.oldPassword}
isPassword={true}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
touched={formik.touched.oldPassword}
errorMsg={formik.errors.oldPassword}
width={451}
/>
<InputText
name={"New password"}
propName={"password"}
value={formik.values.password}
isPassword={true}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
touched={formik.touched.password}
errorMsg={formik.errors.password}
width={451}
/>
<InputText
name={"Confirm new password"}
propName={"passwordConfirm"}
value={formik.values.passwordConfirm}
isPassword={true}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
touched={formik.touched.passwordConfirm}
errorMsg={formik.errors.passwordConfirm}
width={451}
/>
<Button
isSubmit={true}
primary={true}
width={'451px'}
textCenter={true}>
Save
</Button>
</form>
</div>
)
}
And validation scheme:
import * as Yup from 'yup';
const UserPasswordChangeSchema = Yup.object({
oldPassword: Yup.string()
.trim()
.required("Password is required"),
password: Yup.string()
.trim()
.required("New password is required")
.min(8, "The password must contain 8 - 24 characters")
.max(24, "The password must contain 8 - 24 characters"),
passwordConfirm: Yup.string()
.trim()
.oneOf([Yup.ref("password"), null], "Passwords must match")
});
export { UserPasswordChangeSchema }
I wiil be very appreciated for your help
Try to import the Field and ErrorMessage component from Formik
import { Field, ErrorMessage } from 'formik';
and replace the InputText for this new Field something like:
<Field
placeHolder={"Current password"}
type="password"
name="oldPassword"
className="form-control"
value={formik.values.oldPassword}
/>
<ErrorMessage name="oldPassword" />
and repeat for the others inputs but changes the data.
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 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;