Conditional validation of multiple fields using Yup - formik

I have 3 input fields (name, id, and postcode), and I use formik along with Yup for the validation.
What I want to achieve here is, I want to match each input field with a known combination of name, id and postcode (I have a predefined default value for id, name, postcode).
And if the values entered on all the 3 input fields exactly match with the default values of name, id, and postcode, then I have to show the formik error on each of the fields(*something like default not allowed). If one of these fields is different from the default values, do not show the error on any fields.
For eg, if my default values for each fields are name="testName", id="testID", postCode="testPostCode", show validation error on each field only if all 3 input values matches with the defaultValues.
This is what I've now:
const defaultValues = {
name: 'testName',
id: 'testID',
postCode: 'testPostCode'
}
const YUP_STRING = Yup.string().ensure().trim();
const validationSchema = yup.object().shape({
name: YUP_STRING.required('required'),
id: YUP_STRING.required('required'),
postcode: YUP_STRING.required('required'),
})
I've tried several variations, but nothing worked here. Can anyone help me find a solution for this?

You can do something like <Field validate={(value)=>validate(value,values)} name="name" type="text" />
In more detail..
<Formik
initialValues={defaultValues}
onSubmit={values => alert(JSON.stringify(values)}
>
{({ errors, touched, values }) => (
<Form>
<Field validate={(value) => validate(value, values)} name="name" type="text" />
{errors.name && touched.name ? <div>{errors.name}</div> : null}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
And define validate function as
const validate = (value, values) => {
if(values === defaultValues){
return "Default Values not allowed"
} else return undefined
}
Or if you want to do it with validationSchema you can do something as by adding test function on each of the field, I have only done for name:
const validationSchema = yup.object().shape({
name: YUP_STRING.required('required')
.test('name', "No default please", function (item) {
const currentValues = {
name: this.parent.name,
id: this.parent.id,
postCode: this.parent.postCode
}
return !(currentValues === defaultValues)
}
),
id: YUP_STRING.required('required'),
postcode: YUP_STRING.required('required'),
})
No arrow function to be able to use this

Related

How do I access a Formik context's validationSchema?

tl;dr The result of useFormikContext() doesn't include its validationSchema. Based on its TypeScript types, it exists on the object as an optional property, but for some reason, I don't know how to make it appear.
I have a generic component that takes in a fieldName prop. I think use useField(fieldName) to retrieve the data. Now I want to check if there's a validationSchema, and if there is, do something with it (e.g. determine if the field has a max() test).
const MyComp = ({ fieldName, maxLength, ...props }) => {
// for some reason, validationSchema is always undefined
const { validationSchema } = useFormikContext();
if (maxLength == null && validationSchema) {
// never gets here because validationSchema is always undefined
maxLength = Yup.reach(validationSchema, fieldName)
?.tests.find(t => t.OPTIONS.name === 'max')
?.OPTIONS.params.max;
}
return (
<div>
The maxLength of <code>{fieldName}</code> length is{' '}
<code>{maxLength ?? 'undefined'}</code>.
{'\n'}
Its <code>validationSchema</code> via{' '}
<code>useFormikContext()</code> is{' '}
<b>{
validationSchema === null ? 'null' :
validationSchema ? 'definied' :
'not defined'
}</b>.
</div>
);
};
const schema = Yup.object({
withoutMaxLength: Yup.string(),
withMaxLength: Yup.string().max(10),
withMaxLengthProp: Yup.string()
});
const initialValues = {
withoutMaxLength: "",
withMaxLength: "",
withMaxLengthProp: ""
};
const App = () => (
<Formik
initialValues={initialValues}
onSumbit={() => {}}
validationSchema={schema}
>
<Form>
<MyComp fieldName="withoutMaxLength" />
<MyComp fieldName="withMaxLength" />
<MyComp fieldName="withMaxLengthProp" maxLength={10} />
</Form>
</Formik>
);
Code Sandbox
Result
As you can see above, useFormikContext().validationSchema is always undefined even though it is set as a property of <Formik />. Any idea why or what I can do about it?
It turns out this is a known bug in formik, and has been fixed in v3. My best option appears to be yarn patch, or it would be if formik weren't an external for my library.

Getting dynamic field data using vue.js in laravel

I am creating multiple dynamic fields and the same is posted to the laravel controller methods but i'm able to get the simple fields data (fields that are not dynamically generated) but the array fields that are dynamically generated those values i'm unable to get in the controller method.
<input :name="'students['+studentdetails.id+']['+studentdetails.class+']'" type="text" class="form-control"/>
<input :name="'students['+studentdetails.id+']['+studentdetails.class+']'" type="text" class="form-control"/>
In my controller method when i'm getting it:
printr($request->attributes);
export default {
data() {
...
return {
form: new Form({
title: "",
attributes: [],
}),
};
addStudent() {
const header = {
Authorization: "Bearer " + this.token,
};
this.form
.post(APP_URL + `/api/addStudent`, { headers: header })
.then((response) => {
if (response.status == 200) {
location.replace(APP_URL + "/success");
}
});
},
};
there is not value.
You are now creating [] and students as strings, and they should not be. Remove the string quotes completely, as you are using variables only:
:name="students[studentdetails.id][studentdetails.class]"

How to access values inside validationSchema in formik

values are accessible only if I use validation but not inside validationschema
<Formik
initialValues={initialValues}
validationSchema={validationSchema(values)}
onSubmit={actions.handleSubmit}
>
<Form>
if I use useFormikContext(); values are not accessible inside validationSchema because initialization happens after Formik.
how to solve this problem.
Use validate instead of validationSchema
Pass your Form data as 4th argument in validateYupSchema which represents context and can be accessed later in schema.
Pass your schema as 2nd argument in validateYupSchema.
<Formik
validate={(values) => {
try {
validateYupSchema(values, validationSchema, true, values);
} catch (err) {
return yupToFormErrors(err); //for rendering validation errors
}
return {};
}}
onSubmit={} />
Now we can access any form value using this.options.context inside test function in schema
If you need to validate one formik value conditionally based on another formik values,
you can achieve using when() method chaining inside of validationSchema like so:
const validationSchema = Yup.object().shape({
is_female: Yup.boolean('Select The Pricing'),
female_price: Yup.string().when('is_female', {
is: isFemale => isFemale === true,
then: Yup.string().required('Female Price is required')
}),
male_price: Yup.string().when('is_female', {
is: isFemale => isFemale === false,
then: Yup.string().required('Male Price is required')
})
});
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={actions.handleSubmit}
/>
Hope this helps.

dynamic filter based on record.account value

const filterVal = (record) => ({
name: record.account,
});
<ReferenceArrayInput {...props} source="access_id" reference="awsroles" label="AWS Roles" sort={{ field: 'id', order: 'DESC' }} filter={filterVal}
allowEmpty>
<SelectArrayInput source="id" />
I'd like to add a dynamic filter in the ReferenceArrayInput based on one of the value in record. However, it is not working for some reasons. Any help with this is greatly appreciated.
Thanks!
I ended up doing something like this:
var filterVal = (props) => {
return {AwsAccountId: props.match.params.id};
};
<ReferenceArrayInput source="access_roles" reference="awsroles" label="AWS Roles" sort={{ field: 'id', order: 'DESC' }} filter={filterVal(props)} allowEmpty>
You can try out AOR Dependent Input.
https://github.com/marmelab/aor-dependent-input
You can wrap the SelectArray Input in an HOC. This way you can intercept the record being passed to SelectArray input and change it to your needs.
Something like this.
const higherOrderComp = WrappedComponent => props => {
return <div ><WrappedComponent {...modifyRecord(props)} /></div>
}
const modifyRecord = (props) => {
//do something with props.record .....
}
NOTE - make sure that modifyRecord makes a clone of props and then modifies it. Never try and modify input properties directly.

Form validation with react and material-ui

I am currently trying to add validation to a form that is built using material-ui components. I have it working but the problem is that the way I am currently doing it the validation function is currently being called on every state change in the input (i.e. every letter that is typed). However, I only want my validation to occur once the typing has stopped.
Given my current code:
class Form extends React.Component {
state = {open: false, email: '', password: '', email_error_text: null, password_error_text: null, disabled: true};
handleTouchTap() {
this.setState({
open: true,
});
}
isDisabled() {
let emailIsValid = false;
let passwordIsValid = false;
if (this.state.email === "") {
this.setState({
email_error_text: null
});
} else {
if (validateEmail(this.state.email)) {
emailIsValid = true
this.setState({
email_error_text: null
});
} else {
this.setState({
email_error_text: "Sorry, this is not a valid email"
});
}
}
if (this.state.password === "" || !this.state.password) {
this.setState({
password_error_text: null
});
} else {
if (this.state.password.length >= 6) {
passwordIsValid = true;
this.setState({
password_error_text: null
});
} else {
this.setState({
password_error_text: "Your password must be at least 6 characters"
});
}
}
if (emailIsValid && passwordIsValid) {
this.setState({
disabled: false
});
}
}
changeValue(e, type) {
const value = e.target.value;
const nextState = {};
nextState[type] = value;
this.setState(nextState, () => {
this.isDisabled()
});
}
login() {
createUser(this.state.email, this.state.password);
this.setState({
open: false
});
}
render() {
let {open, email, password, email_error_text, password_error_text, disabled} = this.state;
const standardActions = (
<FlatButton
containerElement={<Link to="/portal" />}
disabled={this.state.disabled}
label="Submit"
onClick={this.login.bind(this)}
/>
);
return (
<div style={styles.container}>
<Dialog
open={this.state.open}
title="Enter Your Details to Login"
actions={standardActions}
>
<span className="hint--right hint--bounce" data-hint="Enter in your email address">
<TextField
hintText="Email"
floatingLabelText="Email"
type="email"
errorText={this.state.email_error_text}
onChange={e => this.changeValue(e, 'email')}
/>
</span>
<br />
<span className="hint--right hint--bounce" data-hint="Enter your password">
<TextField
hintText="Password"
floatingLabelText="Password"
type="password"
errorText={this.state.password_error_text}
onChange={e => this.changeValue(e, 'password')}
/>
</span>
</Dialog>
<h1>VPT</h1>
<h2>Project DALI</h2>
<RaisedButton
label="Login"
primary={true}
onTouchTap={this.handleTouchTap.bind(this)}
/>
</div>
);
}
}
export default Form;
Is there a way that I can achieve my desired functionality, without making a major change to the code, or does it need to be completely refactored?
Does the check have to happen after a certain delay? A solution that I think would suffice in most situations would be to split your code up a bit. Don't trigger your isDisabled() function in changedValue(). Instead have it run on the onBlur event instead.
Try this:
<TextField
hintText="Password"
floatingLabelText="Password"
type="password"
errorText={this.state.password_error_text}
onChange={e => this.changeValue(e, 'password')}
onBlur={this.isDisabled}
/>
and then your function becomes:
changeValue(e, type) {
const value = e.target.value;
const nextState = {};
nextState[type] = value;
this.setState(nextState);
}
Current Material-UI version doesn't use the errorText prop but there is still a way that you can use to display error and apply validation to the TextField in Material-UI.
We use the error(Boolean) property to denote if there is an error or not. Further to display the error text use helperText property of the TextField in the Material-UI, just provide it the error text you want to display.
Do it like:
<TextField
value={this.state.text}
onChange={event => this.setState({ text: event.target.value })}
error={text === ""}
helperText={text === "" ? 'Empty!' : ' '}
/>
Simplest is to call form.reportValidity(). form can be obtained by calling event.currentTarget.form.
This library that I had created, takes care of everything related to validating fields and it supports material-ui components as well...
To validate your fields, you just need to wrap you field component and you are done... saving a lot of effort in managing state yourself manually.
<Validation group="myGroup1"
validators={[
{
validator: (val) => !validator.isEmpty(val),
errorMessage: "Cannot be left empty"
}, ...
}]}>
<TextField value={this.state.value}
className={styles.inputStyles}
style={{width: "100%"}}
onChange={
(evt)=>{
console.log("you have typed: ", evt.target.value);
}
}/>
You can use onblur text field event. It's triggered when input looses focus.

Resources