Make yup validate differently depending on what function I call - reactjs

https://codesandbox.io/s/nice-cohen-k3kdtq?file=/src/App.js Here is the codesandbox example of my code
What I need to do is when I click the 'Preview' button I want to disable validation on last two fields (price, category) and when I click 'Submit' I want to validate all the fields. I tried to change react-hook-form resolver depending on state but it doesn't let me and had an idea about making fields not required when boolean variable from component changes but I don't know how can I send this variable to yup schema
const nftSchema = yup.object().shape({
NFTCollectionAddress: yup
.string()
.required("Collection address is required")
.test("len", "Not a valid address", (val) => val.length === 42)
.matches("0x", "Not a valid address"),
NFTTokenID: yup
.number()
.typeError("You must specify a number")
.required("Token ID is required"),
price: yup
.string()
.required("Price is required")
.test("inputEntry", "The field should have digits only", digitsOnly)
.test(
"maxDigitsAfterDecimal",
"Number cannot have more than 18 digits after decimal",
(number) => /^\d+(\.\d{1,18})?$/.test(number)
),
category: yup.string().required("Category is required")
});
export default function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm({
resolver: yupResolver(nftSchema),
});
const onSubmit = (data) => {
};
const handlePreview = (data) => {
};
return (
<form>
<h4>Token ID</h4>
<input
name="NFTTokenID"
type="text"
{...register("NFTTokenID")}
/>
<h4>Collection</h4>
<input
name="NFTCollectionAddress"
type="text"
{...register("NFTCollectionAddress")}
/>
<h4>Price</h4>
<input
name="price"
type="text"
{...register("price")}
/>
<h4>Category</h4>
<input
name="category"
type="text"
{...register("category")}
/>
<button onClick={handleSubmit(onSubmit)}>Submit</button>
<button onClick={handleSubmit(handlePreview)}>Preview</button>
</form>
</div>
);
}

What about creating a different schema for preview, and changing the schema passed to yupResolver based on isPreview? On Preview button also would just set the isPreview state, not use the handleSubmit function.

Related

Yup: how to compare one field with other? Ref neither parent.field not working

This is the yup schema:
let schema = yup.object().shape({
password: yup.string().required('Enter password'),
confirm: yup.string().required('Re-enter password')
.test('passwords-match', 'Password and confirmation do not match', function(value){
const t = yup.ref('password'); // some bulky object
const t1 = t.getValue(); // undefined too
const t2 = this.parent.password; // undefined too
const a = this.resolve(t) // undefined even after entering value
return a === value
})
For some reason, comparison with another field is not working.
What am I missing?
Some idea just came to me. Maybe something is wrong with this validator I'm using from here: https://stackblitz.com/edit/react-97lr5s?file=index.js
The full code from there:
import * as yup from 'yup';
let schema = yup.object().shape({
name: yup.string().required(),
age: yup
.number()
.required()
.typeError('Number only.')
.positive()
.integer()
.round(),
a: yup
.number()
.required()
.typeError('Number only.')
.positive()
.integer()
.round().test('', 'asdf', function(v){
console.log(this.parent.name) // undefined
return v == this.parent.age}), // okay?
});
const yupSync = {
async validator({ field }, value) {
await schema.validateSyncAt(field, { [field]: value });
},
};
const DynamicRule = () => {
const [form] = Form.useForm();
return (
<Form form={form} name="form1">
<Form.Item name="name" label="Name" rules={[yupSync]}>
<Input placeholder="Please input your name" />
</Form.Item>
<Form.Item name="age" label="Age" rules={[yupSync]}>
<Input placeholder="Please input age" />
</Form.Item>
<Form.Item name="a" label="Age" rules={[yupSync]}>
<Input placeholder="Please input a" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
Here is how you can compare two fields in Yup. You're almost there but just need some additional info in order to compare two fields.
Working example:
Yup.object().shape({
password: Yup.string().min(8).required("Password is required"),
confirmPassword: Yup.string()
.when("password", { // this should match with input field name
is: (val) => (val && val.length > 0 ? true : false),
then: Yup.string().oneOf(
[Yup.ref("password")],
"Both Password need to be same"
),
})
.required("Confirm Password is required"),
}),

React: Yup conditional validation only when a field is visible

I'm trying to implement form validation using formik & yup in React. I have a login/register form with three fields (name,email,password). Name field is conditionally rendered when 'create an account' button is clicked. I want the name field to be required only when form is in register state. I'm using a state variable isLogin to save the form's current state, also using it to initialise showName boolean in formik's initialValues. Right now, name field has no validation applied on it and form can be submitted if name field is empty.
My Code
const [isLogin, setIsLogin] = useState(true);
const initialAuthFormValues = {
hideName: isLogin,
name: "",
email: "",
password: "",
};
My Validation Schema
const authFormValidationSchema = Yup.object().shape({
hideName: Yup.boolean(),
name: Yup.string().when("hideName",{
is: false,
then: Yup.string().required("Name is required"),
}),
email: Yup.string().required("Email is required"),
password: Yup.string().required("Password is required"),
});
My Component looks like this
<Formik
initialValues={initialAuthFormValues}
validationSchema={authFormValidationSchema}
onSubmit={submitHandler}
>
{(formik) => (
<Form>
{!isLogin && (
<TextField
label="Name"
type="text"
name="name"
placeholder="Name"
/>
)}
<TextField
label="Email"
type="text"
name="email"
placeholder="Email"
/>
<TextField
label="Password"
type="password"
name="password"
placeholder="Password"
/>
<div className={classes.actions}>
<Button type="submit"> {isLogin ? "Login" : "Create Account"} </Button>
<Button
type="reset"
className="toggle"
onClick={() => { setIsLogin((prevState) => !prevState); }}
>
{isLogin ? "Create new account" : "Login with existing account"}
</Button>
</div>
</Form>
</section>
)}
</Formik>
You can use authFormValidationSchema as a state , and update this state in case that button clicked :
const authFormValidationSchema = {
email: Yup.string().required("Email is required"),
password: Yup.string().required("Password is required"),
};
const [authFormValidationSchema, setAuthFormValidationSchema] = useState(Yup.object());
track the status when button is clicked in a useEffect, and update state of authFormValues accordingly :
useEffect(()=>{
let authFormValidationSchema_ = JSON.parse(JSON.stringify(authFormValidationSchema))
if(!buttonIsClicked)
setAuthFormValidationSchema(Yup.object(authFormValidationSchema_));
else setAuthFormValidationSchema(Yup.object({...authFormValidationSchema_, name: Yup.string().required("Name is required") }));
},[buttonIsClicked]);
Finally found the solution to the problem : In my code, action is my prop
First, create a variable in your JS that will hold the logic:
let passwordTest
if (action === "Register") {
passwordTest = {
password2: Yup
.string().trim()
.required("Please confirm your password")
.oneOf([Yup.ref('password'), null], 'Passwords must match')
} else {
passwordTest = null
}
Basically above you are saying that if the props is Register (if this
form is rendered for registering purpose and not login), then create
the field apply the validation listed, otherwise, this field will be
null.
Then, in your Yup schema destructure your variable :
const validationSchemaYup = Yup.object().shape({
other fields validation logic...
...passwordTest,
other fields validation logic...
Here you add your new custom validation logic, don't forget to destructure!

react-hook-form use mutiple resolver

i am trying to create a general input like this
const TextInput = ({
name,
register = () => {},
errors,
serverErrors,
...props
}) => {
return (
<div >
<input
type="text"
{...register(name, {
pattern: { value: /^[A-Za-z]+$/i, message: "Invalid Input" },
})}
{...props}
/>
{errors?.[name] && (
<span className="text-errorColor">{errors?.[name]?.message}</span>
)}
</div>
);
};
I will use this input in form and use Yup to validate this form
const schema = yup
.object({
first_name: yup
.string()
.required("This field is required")
.max(20, "max 20 characters"),
})
.required();
const SignupForm = ({ signUpContact, signUpType }) => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<TextInput
name="first_name"
register={register}
errors={errors}
serverErrors={error}
placeholder="First Name"
/>
</form>
);
};
but the problem is that the validations in TextInput Competent aren't running
i think i can't use Register Validation with Yup validation.
as you see I won't duplicate validation [A-Za-z] every time I use TextInput, is there any way to do this?
For input just register the field using react-hook-form
<input
type="text"
{...register(name)}
{...props}
/>
Use yup to handle all the validation logic
yup
.string()
.trim()
.matches(/^[A-Za-z]+$/i , 'Invalid Input')
.max(20, "max 20 characters"),
.required();

Different yup validation modes for different inputs with react

I want to have different validation modes for different inputs. Currently, the form validation uses Yup with the mode: "onTouched", but the problem is this mode applies to all inputs in the form.
I've built a demo with the same principles to demonstrate this: https://codesandbox.io/s/react-playground-forked-8qb0k?file=/Pokemon.js - if you click in any of the 2 inputs and then click away, the error will show (if validation fails that is). I want one of the inputs to not have an onTouched mode. How can this be achieved?
Currently it's set up as follows:
const schema = Yup.object().shape({
name: Yup.string()
.required("Required")
.min(3, "Enter at least 3 characters"),
test: Yup.string()
.required("Required Test")
.min(2, "Enter at least 2 characters")
});
const {
register,
handleSubmit,
setError,
formState: { errors },
trigger,
setValue,
watch,
clearErrors
} = useForm({
resolver: yupResolver(schema),
mode: "onTouched"
// reValidateMode: "onChange"
});
And the form:
<form onSubmit={handleSubmit(onSubmit /*, onError*/)}>
<input
{...register("name", { required: true })}
name="name"
placeholder="Enter a pokemon"
onChange={onNameChange}
/>
{errors.name && <p>{errors.name.message}</p>}
<input
{...register("test", { required: true })}
name="test"
placeholder="test"
/>
{errors.test && <p>{errors.test.message}</p>}
<button type="submit" onClick={onSubmit}>
Show Pokemon
</button>
{errors.namey && <p>{errors.namey.message}</p>}
</form>
Thanks

How to set and get a datepicker value using antd with formik?

Here i am creating Datepicker with antd and passing this antd datepicker to formik field.My sample code for Datepicker with antd
import React from "react";
import { Form, DatePicker } from "antd"
import { Field } from "formik";
import moment from 'moment';
const FormItem = Form.Item;
function onChange(date, dateString) {
console.log(date, dateString);
}
const dateFormat = "MM-DD-YYYY"
// Here i am adding antd error message through DateInput
const DateInput = ({
field,
form: { touched, errors },
...props
}) => {
const errorMsg = touched[field.name] && errors[field.name]
const validateStatus = errorMsg ? "error"
: (touched[field.name] && !errors[field.name]) ? "success"
: undefined
return (
<div>
<FormItem
label={props.label}
help={errorMsg}
validateStatus={validateStatus}
hasFeedback
{...props.formitemlayout}>
<DatePicker onChange={onChange} defaultPickerValue={moment()}/>
</FormItem>
</div>
)
}
export default DateInput
i am adding this ant component to formik field component,submit the form using handleSubmit and applying the YUP validations. iam getting a problem was submitting the form iam getting the required validation of DatePicker, and problem is selecting the values of DatePicker iam not getting the value and validation message is displayed after submitting the form.
class FormikApollo extends React.Component {
render() {
const { values, handleSubmit, setFieldValue } = this.props
return (
<div align="center">
<Form onSubmit={handleSubmit}>
<Field
name="username"
label="Name"
placeholder="Enter a Name"
component={TextField}
value={values.username}
formitemlayout={formItemLayout}
/>
<Field
name="email"
label="Email"
placeholder="Enter an Email"
component={TextField}
value={values.email}
formitemlayout={formItemLayout}
/>
<Field
name="password"
label="Password"
type="password"
placeholder="Enter a Password"
component={TextField}
formitemlayout={formItemLayout}
/>
<Field
name="dateofbirth"
label="dateOfBirth"
type="date"
component={DateInput}
formitemlayout={formItemLayout}
defaultValue={values.dateofbirth}
format={dateFormat}
/>
<Button type="primary" htmlType="submit">Submit</Button>
</Form>
)
}
}
Here i am getting the values through withFormik and submitting the form using handleSubmit. Why iam not getting datepicker value and why validation message is displayed after selecting a datepicker value?
const FormikApp = (withFormik)({
mapPropsToValues({ username, email, password, dateofbirth }) {
return {
username: username || '',
email: email || '',
password: password || '',
dateofbirth: dateofbirth || ''
}
},
validationSchema: Yup.object().shape({
username: Yup.string()
.min(3, "Username must be above 3 characters")
.required("Username is required"),
email: Yup.string()
.email("Invalid Email !!")
.required("Email is required"),
password: Yup.string()
.min(6, "Password must be above 6 characters")
.required("Password is required"),
dateofbirth: Yup.string().required("Date is required")
}),
handleSubmit(values, { resetForm }) {
resetForm();
console.log(values)
}
})(FormikApollo)
In your DateInput component try to set value with setFieldValue() method of Formik whether it is valid or not. I believe you can extract it from via: form: { touched, errors, setFieldValue }.
Also check touched items in your form, and make sure that you are changing the value of your date field.

Resources