Simple react form validation with yup - reactjs

I'm trying to learn form validation with yup and react-hook-form and, ideally want to set the validation to also require at least one of the two different-named radio options, but first understand why the isValid variable is false.
Here's my code:
import React from "react";
import { useForm } from "react-hook-form";
import * as yup from "yup"
const App = () => {
const { register, handleSubmit, watch, errors } = useForm();
const onSubmit = async data => {
console.log('form data', data);
const isValid = await schema.isValid();
console.log('isValid', isValid);
}
return (
<>
{console.log('errors', errors)}
<form onSubmit={handleSubmit(onSubmit)}>
<input name="name" defaultValue="test" ref={register} />
<input name="nameTwo" ref={register({ required: true })} />
{errors.exampleRequired && <span>This field is required</span>}
<label>
<input
name="test"
type="radio"
ref={register}
value="test"
/>
</label>
<label>
<input
name="testTwo"
type="radio"
ref={register}
value="testTwo"
/>
</label>
<input type="submit" />
</form>
</>
);
}
const schema = yup.object().shape({
name: yup.string(),
nameTwo: yup.string().required(),
test: yup.string(),
testTwo: yup.string()
})
export default App
So the schema defines only one field (nameTwo) as required, but even when filling in that field it still is false. Why is isValid not true? And how is it best to go about making either one of the two radio buttons (test or testTwo) required?
Stackblitz demo: https://stackblitz.com/edit/react-93vahd
Thank you

The isValid property is available in the formState object returned from the useForm hook.
You'll notice that onSubmit is not called unless the Yup validation passes. Therefore isValid will always be true when checking inside the onSubmit callback.
import React from 'react';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
const App = () => {
const {
register,
handleSubmit,
errors,
formState: { isValid }
} = useForm();
const onSubmit = async data => {
console.log('form data', data);
};
return (
<>
{console.log('isValid', isValid)}
{console.log('errors', errors)}
<form onSubmit={handleSubmit(onSubmit)}>
<input name="name" defaultValue="test" ref={register} />
<input name="nameTwo" ref={register({ required: true })} />
{errors.exampleRequired && <span>This field is required</span>}
<label>
<input name="test" type="radio" ref={register} value="test" />
</label>
<label>
<input name="testTwo" type="radio" ref={register} value="testTwo" />
</label>
<input type="submit" />
</form>
</>
);
};
const schema = yup.object().shape({
name: yup.string(),
nameTwo: yup.string().required(),
test: yup.string(),
testTwo: yup.string()
});
export default App;

Related

React radio buttons not displaying in browser

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"
.....
/>

Yup Validation causing errors

I'm trying to validate my forms on React using yup and useForm, I've followed every step of the tutorial but I am still receiving errors. I am receiving 'TypeError: Cannot read property 'firstName' of undefined'. This is odd because I have already named it in the input value. How can I fix this?
P.s I have tried using {...register} and ref={register} don't seem to matter.
import React from 'react'
import { useForm } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import * as yup from "yup";
const schema = yup.object().shape({
firstName: yup.string().required("First Name should be required please"),
lastName: yup.string().required(),
email: yup.string().email().required(),
});
const Submission = () => {
const { register, handleSubmit, errors } = useForm({
resolver: yupResolver(schema),
});
const submitForm = (data) => {
console.log(data);
};
return (
<div className="Form">
<div className="title">Sign Up</div>
<div className="inputs">
<form onSubmit={handleSubmit(submitForm)}>
<input
type="text"
name="firstName"
{...register('firstName')}
placeholder="First Name..."
/>
<p> {errors.firstName?.message} </p>
<input
type="text"
name="lastName"
placeholder="Last Name..."
ref={register}
/>
<p> {errors.lastName?.message} </p>
<input
type="text"
name="email"
placeholder="Email..."
ref={register}
/>
<p> {errors.email?.message} </p>
<input type="submit" id="submit" />
</form>
</div>
</div>
);
}
export default Submission;
The exact error I am recieving looks like this:
Turns out I'm still using the outdated version of the library.
From this,
<input type="text"name="email"placeholder="Email..."ref={register}/>
const { register, handleSubmit, errors } = useForm({
resolver: yupResolver(schema),
});
To this
{...register("email", { required: "Email is Required"})};
const { register,handleSubmit, formState: { errors },} = useForm();

Why defaultValue from React hook form can not work in react

I stuck in a problem. I am using React Hook Form
in my shipment component everything has gone well but while i try to use react hook form for my shipment component and setting default value on these component it's not work accurately. My code is given below:
import React, { useContext } from 'react';
import { useForm } from 'react-hook-form';
import { UserContext } from '../../App';
import './Shipment.css';
const Shipment = () => {
const { register, handleSubmit, watch, formState: { errors } } =
useForm();
const [loggedInUser,setLoggedInUser] = useContext(UserContext);
const onSubmit = data => {
console.log('form submitted',data);}
console.log(watch("example")); // watch input value by passing the name
of it
return (
<form className="ship-form" onSubmit={handleSubmit(onSubmit)}>
{/* <input defaultValue={loggedInUser.email}
{...register("example")} /> */}
<input name="name" defaultValue={loggedInUser.name}
{...register("exampleRequired", { required: true })} placeholder="Your
Name" />
{errors.name && <span className="error">Name is required</span>}
<input name="email" defaultValue={loggedInUser.email}
{...register("exampleRequired", { required: true })}
placeholder="Email address"/>
{errors.email && <span className="error">Email is required</span>}
<input name="address" {...register("exampleRequired", { required:
true })} placeholder="address"/>
{errors.address && <span className="error">Address is
required</span>}
<input name="phone" {...register("exampleRequired", { required: true
})} placeholder="Phone number"/>
{errors.phone && <span className="error">Phone Number is
required</span>}
<input type="submit" />
</form>
);
};
export default Shipment;
when i attempt to sign in by using email :
i set default value on name and email field but it's not work except name
and my console show only name except all data
Actually i need all data and set default value on a specific field
I think the problem is here:
Before
<input name="email" defaultValue={loggedInUser.email}
{...register("exampleRequired", { required: true })}
placeholder="Email address"/>
After:
<input defaultValue={loggedInUser.email}
{...register("email", { required: true })}
placeholder="Email address"/>
All of your register have the same name, that's why it doesn't work.
See the register API

Conditional validation with react hook form

Here is my form looks like and also CodeSanbox. currently I'm using react-hook-form
as you can see form has 3 inputs. Submit button should be disabled until all the required fields are entered.
Two use case:
If "Check" is unchecked:
only "id" should be validated and submit button should get enabled. "firt" and "last" names should not be part of form data
If "Check" is checked
all the fields should be validated
first and last names are only required if "Check" is checked. so its not checked then form should only validate "ID" field. if "Check" is checked then all fields should get validated.
problem I'm having is if I enter id, form state is still "invalid". Form is expecting to enter values for first and last name.
I would appreciate any help.
I have updated your CodeSanBox code and also adding the full code here:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";
import "./index.css";
function App() {
const {
register,
handleSubmit,
errors,
formState,
unregister,
setValue,
getValues,
reset
} = useForm({
mode: "onBlur",
reValidateMode: "onBlur",
shouldUnregister: true
});
//console.log(formState.isValid);
console.log(errors);
const [disabled, setDisabled] = useState(true);
const onSubmit = (data) => {
alert(JSON.stringify(data));
};
useEffect(() => {
// #ts-ignore
if (disabled) {
console.log("unregister");
reset({ ...getValues(), firstName: undefined, lastName: undefined });
unregister(["firstName", "lastName"]);
} else {
console.log("register");
register("firstName", { required: true });
register("lastName", { required: true });
}
}, [disabled]);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="id">ID</label>
<input
name="id"
placeholder="id"
ref={register({ required: true, maxLength: 50 })}
/>
{errors.id && <p>"ID is required"</p>}
<fieldset disabled={disabled}>
<legend>
<input
type="checkbox"
name={"name"}
ref={register}
onClick={() => setDisabled(!disabled)}
/>
<span>Check</span>
</legend>
<label htmlFor="firstName">First Name</label>
<input
name="firstName"
placeholder="Bill"
onChange={(e) => {
console.log(e.target.value);
setValue("firstName", e.target.value);
}}
ref={register({ required: !disabled })}
/>
{errors.firstName && <p>"First name is required"</p>}
<label htmlFor="lastName">Last Name</label>
<input
name="lastName"
placeholder="Luo"
onChange={(e) => setValue("lastName", e.target.value)}
ref={register({ required: !disabled })}
/>
{errors.lastName && <p>"Last name is required"</p>}
</fieldset>
<input type="submit" disabled={!formState.isValid} />
</form>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
First I found that you set disabled state as false which should be true as an initial value, and regarding the issue, I have used reset and getValues functions when the disabled state changes.
EDIT for you to recognize code changes easy, I have restored all the code at CodeSanBox.
This whole validation behavior (UX) is definitely making things a bit harder, however, there are a couple of things that you should leverage from the library such as:
watch
validate
getValues
import React from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";
import "./index.css";
function App() {
const {
register,
handleSubmit,
errors,
formState: { isValid, touched },
getValues,
trigger,
watch
} = useForm({
mode: "onBlur"
});
const onSubmit = (data) => {
alert(JSON.stringify(data));
};
const validate = (value) => {
if (getValues("name")) { // read the checkbox value
return !!value;
}
return true;
};
const isChecked = watch("name"); // watch if the name is checked
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="id">ID</label>
<input
name="id"
placeholder="id"
ref={register({ required: true, maxLength: 50 })}
/>
{errors.id && <p>"ID is required"</p>}
<fieldset disabled={!isChecked}>
<legend>
<input
type="checkbox"
name={"name"}
ref={register}
onChange={() => trigger()} // you want update isValid due to state change, and also those extra two inputs become required
/>
<span>Check</span>
</legend>
<label htmlFor="firstName">First Name</label>
<input
name="firstName"
placeholder="Bill"
ref={register({
validate
})}
/>
// make sure input is touched before fire an error message to the user
{errors.firstName && touched["firstName"] && (
<p>"First name is required"</p>
)}
<label htmlFor="lastName">Last Name</label>
<input
name="lastName"
placeholder="Luo"
ref={register({
validate
})}
/>
{errors.lastName && touched["lastName"] && (
<p>"Last name is required"</p>
)}
</fieldset>
<input type="submit" disabled={!isValid} />
</form>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CSB:
https://codesandbox.io/s/react-hook-form-conditional-fields-forked-n0jig?file=/src/index.js:0-1831
on your ref, dont use hard coded bool true, ref={register({ required: true})}, but your dynamic ref={register({ required: disabled })}
do notice that because your mode: "onBlur" config, the button won't be abled until id field blurred
You just need to replace true .from ref: required:true..... Instead use const 'disabled' ....in input of first and last name .
So as to achieve dynamic change

React-hook-form input fields match validation best practice

What's the best practice when doing input fields match validation when dealing with React-hook-form? For example, when matching email inputs, etc.
While looking into email match validation with React-hook-form found an issue while trying to separate error messages from "coupled elements" through their validation method. The ref only takes one argument that is used for React-hook-form register, while needing to use useRef to access the current.value for value matching, as follows:
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";
function App() {
const { register, handleSubmit, errors } = useForm();
const inputEmail = useRef(null)
const onSubmit = data => {
console.log('onSubmit: ', JSON.stringify(data))
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="email">Email</label>
<input
name="email"
type="email"
ref={inputEmail}
/>
{/* desired: show `email` error message */}
<label htmlFor="email">Email confirmation</label>
<input
name="emailConfirmation"
type="email"
ref={register({
validate: {
emailEqual: value => (value === inputEmail.current.value) || 'Email confirmation error!',
}
})}
/>
{errors.emailConfirmation && <p>{errors.emailConfirmation.message}</p>}
<input type="submit" />
</form>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
While this pattern seems to be an option when doing input field matching it does not play well with React-hook-form!
For example, the error message is coupled with one input case only and has no separate messages for each independent field, or one of the input fields does not have the register assigned to it, this means that the property required is not set, etc.
So, I'm looking into a good practice or pattern that solves:
Keeping error messages separated by the input field
The validation method, when testing the match should be able to reference the twin field value in a React compliant way and not
through the DOM (document.querySelector, etc)
You shouldn't need the manual ref for inputEmail. Instead, use the getValues method to fetch the current value of your whole form.
const { register, getValues } = useForm()
Then you register both inputs and call getValues from your custom validation.
<input
name="email"
type="email"
ref={register}
/>
<input
name="emailConfirmation"
type="email"
ref={register({
validate: {
emailEqual: value => (value === getValues().email) || 'Email confirmation error!',
}
})}
/>
For this you could use Yup library, which is great:
Add validationSchema to your config object when instantiating useForm and pass a valid Yup schema. Like so:
const Schema = yup.object().shape({
email: yup.string().required('Required field'),
emailConfirmation: yup
.string()
.oneOf([yup.ref('email')], 'Emails must match')
.required('Required field'),
});
// How to add it to your useForm
const { register } = useForm({
validationSchema: Schema
})
Your component should look something like this:
function App() {
const { register, handleSubmit, errors } = useForm({
validationSchema: Schema
});
const onSubmit = data => {
console.log('onSubmit: ', JSON.stringify(data))
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="email">Email</label>
<input
name="email"
type="email"
ref={register}
/>
{/* desired: show `email` error message */}
<label htmlFor="email">Email confirmation</label>
<input
name="emailConfirmation"
type="email"
ref={register}
/>
{errors.emailConfirmation && <p>{errors.emailConfirmation.message}</p>}
<input type="submit" />
</form>
);
}

Resources