conditional validation with yup - javascript

I have 3 fields in a form, the "type" field is select and from that list, some items enable or disable the "out" field, if is enable I need the "out" field to be less than the "in" field and vice-versa, but if the "out" field is disabled I don't need that validation, I was trying something with .when, but is not working, any ideas on how to do this?
const [disableOutCounterField, setDisableOutCounterField] = useState(false);
const schema = yup.object().shape({
type: yup.string().required(requiredMessage),
in: yup
.number(numberMessage)
.required(requiredMessage)
.integer(integerMessage)
.min(1, positiveMessage)
.typeError(numberMessage)
.when("out", {
is: !disableOutCounterField,
then: yup.number().moreThan(yup.ref("out"), moreThanMessage),
message: moreThanMessage,
}),
out: yup
.number(numberMessage)
.integer(integerMessage)
.typeError(numberMessage)
.lessThan(yup.ref("in"), lessThanMessage),
});

The construct:
.when("out", {
is: !disableOutCounterField,
compares the out value with !disableOutCounterField, and if they are equal, the then rule is applied. But quite likely they are never the same.
The check that is needed here is just the value of !disableOutCounterField by itself, for any value of out. This can be done using an expression:
.when("out", {
is: value => !disableOutCounterField,
In words: for every out value, return !disableOutCounterField, and if it returns true, apply the then part.

An alternative I found for this is to regenerate the object validationSchema from inside a useEffect.
This has the benefit of using useState without having them mapped to specific form fields ahead of time, such as if you set something conditionally from the database and don't want to set it as a hidden field somewhere.
const validationPiece = yup.object({
amount: yup
.number()
.typeError('Please enter an amount')
.required('Please enter an amount')
});
const [validationSchema, setValidaitonSchema] = useState(yup.object({}));
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm({
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (needNewPartOfForm) {
const validationSchemaDict = {};
if (needNewPartOfForm.xyz) {
validationSchemaDict['xyz'] = validationPiece; // does this need a cloneDeep?
}
if (needNewPartOfForm.abc) {
validationSchemaDict['abc'] = validationPiece;
}
setValidaitonSchema(yup.object(validationSchemaDict));
}
}, [clientDocs, clientId, reset, teamId]);

Related

Yup How to get the value of field using .when on the same field?

How to get the value of field when using when on the same field ?
email: yup
.string()
.when([], {
is: (val: string) => {
console.log('val = ', val)
return true
},
then: (schema) => {
console.log('schema = ', schema)
return schema
},
})
})
I am assuming you want to get the value of email in when condition at is location as well as at then location. then you have wrote everything correctly. just remove starting [] empty array in when condition. It you don't provide any array or string then value of current item will get at is location.
ref: React-hook-form validation when input is empty. Conflict with Yup.matches
I have updated the below as per your latest comment. I have tested this in my JavaScript file. so you might need to convert it as per TSX syntax.
email: yup
.string()
.when({
is: (val) => {
console.log('val = ', val)
return true
},
then: yup.string()
.test('testKey', 'error', (value) => console.log('value at then', value)),
})
})

Why yup is trigger my tests even though previous tests are failing?

I want to validate my object with the schema using yup.
But I notice that when I wrote my own test function it's trigger anyway.
I mean I validate the age property for number, null, positive, integer value. then I want to continue with my own logic test.
So I expect to NOT enter the function unless the previous tests are valid.
I'm not sure if this is how it meant to be, but in this way I must also check for valid input in my tests function, even though I add the number, null, positive, integer checks.
So am I using the yup wrong?
What I expect form yup is not invoke the test if the previous tests are invalid.
stackblitz
import { object, string, number, date, InferType } from 'yup';
let userSchema = object({
age: number()
.nullable()
.positive()
.integer()
.test({
message: 'test message',
test: (v) => {
console.log('in test!', v);
return !!v.toPrecision();
},
}),
});
userSchema
.validate({ age: null })
.then((res) => {
console.log({ res });
})
.catch((e) => {
console.log({ e });
});

how to update array of objects when input is changed

I have an array of objects
const data= [
{ id:1,name:"apple", city:"ban" },
{ id:2,name:"mango", city:"hyd" }
]
And I have two input fields when the user fills. if !null then I need to add an object to "data" {id:2,name:"apple",city:"nel"} and the id of the 2 in the data object should be 3 and if again the user makes it to null ie deleted the value from the input field need to remove this {id:2,name:"apple",city:"nel"} and make the id from 3 to 2. similarly of the user gives 2 inputs i need to add 2times {id:2,name:"apple",city:"nel"},{id:3,name:"apple",city:"nel"} and make the id of 2 to 4. similarly if the user deletes eaither/or deletes both or single data from the input i need to add or remove the data array accordingly can anyone please help me with this in react with hooks
You can track the form values with a single useState() hook.
You can use both input onChange event handlers, and form onSubmit event handler to build a component that behaves like you want.
The following is an example React component with hooks you can take as a starting point to manage your state. Please take it as a template and tweak it as needed to fulfill your actual requirement.
const storedData = [
{ id: 1, name: 'apple', city: 'ban' },
{ id: 2, name: 'mango', city: 'hyd' },
]
let nextId = 3
const MyFormComponent = () => {
const initialFormData = { name: '', city: '' }
const [formData, setFormData] = useState(initialFormData)
const clearFormData = () => {
setFormData(initialFormData)
}
const handleOnInputChange = (event) => {
const { value, name } = event.target
// merge previous formData with the new value from input
setFormData({
...formData,
[name]: value,
})
}
const handleOnSubmit = (event) => {
// prevent HTML form default handler (navigation, etc.)
event.preventDefault()
const { name, city } = formData
// OPTIONAL: handle form data validations
if (name === '') {
// no "name" provided
alert('Must specify a name!')
return
}
// input1 "name" and input2 "city" are available in formData
// TODO handle the form data and implement your application logic / update storedData / etc.
// TODO a rough example below, please tweak it to match your requirements
const existingEntryByIndex = storedData.findIndex(
({ name }) => formData.name === name
)
if (existingEntryByIndex >= 0 && formData.city === '') {
// name exists and city value is empty => delete this entry
// NOTE: city value can't be null, because it will be always a string. Maybe, you can use "null" string though.
storedData.splice(existingEntryByIndex, 1)
} else {
// name exists and city not empty, or name is new => add a new city
storedData.push({ id: nextId, name, city })
nextId++
}
// --- end of rough example ---
// OPTIONAL: clear the form values ->
clearFormData()
}
return (
<div className={'my-form-container'}>
<form onSubmit={handleOnSubmit}>
<input
type="text"
name="name"
value={formData.name}
onChange={handleOnInputChange}
/>
<input
type="text"
name="city"
value={formData.city}
onChange={handleOnInputChange}
/>
</form>
</div>
)
For further reference, you can check the React docs to learn about more techniques and ways to handle form inputs, and events.

React/JavaScript - Validate two or more inputs

Is it possible to validate two or more inputs in the same file ? As I am trying to create a second validator for another input. I have one validator for a number input -
function FormVal() {
const [phone, setPhone] = React.useState<string>();
const [errors, setErrors] = React.useState<{ phone: string }>();
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { value }
} = event;
setErrors({ phone: '' });
setPhone(value);
let reg = new RegExp(/^\d*$/).test(value);
if (!reg) {
setErrors({ phone: 'Only numbers' });
}
};
}
I would like to create second input like email or text but I don't want to create a new file and I was wondering how can I add another validator there.
Instead of using the above-mentioned approach, What you can do is create a schema to validate the inputs. There are many great libraries for this.
In a large project writing a validator for each type of input can be a complex task. You can use fastest-validator which is the fastest data validation library for javascript.

Yup conditional validation based on a a non field value

I am trying to create a yup schema where based on a variables value the schema changes slightly. In my case depending on the value of prop myCondition I need to make a field as required. I would like to know if there is any better way I can achieve the same using Yup. Here's my current code structure that works:
// config.js
const COMMON_SCHEMA = {
str1: yup
.string()
.nullable()
.required('Please input str1'),
str3: yup
.string()
.nullable()
};
const VALIDATION_SCHEMA_1 = yup.object().shape({
...COMMON_SCHEMA,
str2: yup.string().nullable(),
});
const VALIDATION_SCHEMA_2 = yup.object().shape({
...COMMON_SCHEMA,
str2: yup
.string()
.nullable()
.required('Please input str2'),
});
const SCHEMAS = {
VALIDATION_SCHEMA_1,
VALIDATION_SCHEMA_2
}
export default SCHEMAS;
the following is how I conditionally choose different schemas:
// app.js
import SCHEMAS from './config';
...
<Formik
validationSchema={
this.props.myCondition === true
? SCHEMAS.VALIDATION_SCHEMA_1
: SCHEMAS.VALIDATION_SCHEMA_2
}
>
...
</Formik>
I feel like I can achieve whatever I am doing above in an easier way with yup. One of the approaches I tried was to pass in the prop myCondition into config file and work with it's value by using yup.when() but when only works if the value you want to use is also a part of the form so I couldn't achieve the same.
This can be achieved by the following way:
You can wrap your schema in a function and pass your conditonal prop:
const wrapperSchema = (condition) =>
Yup.object().shape({
...COMMON_SCHEMA,
str2: yup.string()
.when([], {
is: () => condition === true,
then: yup.string().nullable(),
otherwise: yup.string().nullable().required('Please input str2'),
}),
});
Please note that first param of when is a required param, you can also pass e.g. ["title", "name"] where title and name can be your field values, and you can retrieve and use them in is callback, in the same order, if you have to.
You can conditionally add validation directly in the validationSchema, like this: https://stackoverflow.com/a/56216753/8826314
Edited to add, you can include your value in the initial form, so that you can do the when check against that field. For example:
<Formik
initialValues={
...,
str2
}
validationSchema={
...,
str2: yup.object().when('str2', {
is: str2 => condition check that returns boolean,
then: Yup.string().nullable(),
otherwise: Yup.string().nullable().required('Please input str2')
})
}
>
...
</Formik>

Categories

Resources