Question
Using the withFormik as a HOC, with both a validate function and a validateSchema function, seems to result in only validate being called on a handleSubmit, whereas onBlur events trigger validateSchema.
I'm not quite sure here. :) Should both validation types be called upon handleSubmit? I imagine if the answer is "yes", either the results of the errors created by each one would have to be merged (could be messy), or the first one that produces errors stops the other function from being called.
I had both validation types because I had not yet figured out how to handle a few use cases in yup. I think this is a good time to hunker down and figure that out so I only have validationSchema. :)
If both validation types concurrently are not supported, it should be documented, or at least have the library throw an error if it sees both defined in the withFormik call.
I never thought anyone would use both at the same time. Formik source code is probably inconsistent about precedence. I believe in most situations they would race each other resulting in tons of extra renders.
Yup is really nice (as is formik), but I have run into validation situations where it'd be nice to be able to use validateSchema as well as a custom validation function.
Thanks @jaredpalmer for the info. In the end, I figured out how to handle the validation all in yup, which is much cleaner than what I would have done via the validation function. I agree it could be quite problematic dealing with resolving validations between the two approaches.
You can fairly easily replicate what validateSchema does when you define your validate function.
However, I noticed a similar effect when applying the new validate prop of the <Field> component.
You can't have both <Field> and <Form> level validation, they override each other.
I think resolving one of these issue would resolve the other. @jaredpalmer is this something you would like to support? If yes I could work on a PR.
Thanks!
I was actually looking into this same question today, trying to combine errors from both validationSchema and validate() methods. But only validationSchema() errors out the form onChange, whereas validate() does only if I then click again on submit button to trigger formSubmit().
I have an ArrayField and I want to compare for value uniqueness between objects in the ArrayField.
Simple to implement in validate(values), but not sure how to achieve the same with Yup schema?
Any suggestions?
Thanks!
To anyone coming to this, no they were never intended to be used concurrently.
Fair enough, thanks for answering on that point @jaredpalmer.
Still, do you think it is viable to make results from both validate() and validationSchema() get combined into the form.errors? I mean, is there a strong technical hurdle blocking this?
Also I would still like some guidance on how to use just validationSchema() and Yup to check for duplication between form values - or any other cross-field checks. Anyone?
Thanks:)
I would rather not have to call 2 functions. You can just run Yup validation schema.validate on your own within validate and keep a single source of truth in regards to validation. Imho much better
Not documented but Formik exports some Yup helpers too. If you open an issue I can add docs later this week probs
@Romasato Look into Yup's array function.
I've discoved Formik uses a naming convention for nested objects. Given this values structure:
{ subject: "Test", tags: ['issue', 'tech-debt'] }
You can name your form elements: subject, tags[0], and tags[1]. (Most likely you'd be mapping the array values to form elements programatically so the index values are interpolated in the names.)
Then in Yup, you can configure validation for the tags array elements like:
validationSchema: Yup.object().shape({
subject: Yup.string().ensure().required("Subject is required"),
tags: Yup.array().ensure().min(1, "Please add at least one tag.").of(Yup.string().ensure().required("Tag cannot be empty"))
});
In the above schema, yup will require that at least one tag is present, and that each tag is a non-empty string. Thus you can add validation to the array itself, as well as validation on the elements of the array. You could even call Yup's test function at the array level to test the array values together (maybe one has to be present if another one with value "Z" is present--a contrived example). The test function includes helpers assigned to this to let you set where an error should be set in the errors object, for example.
The formik errors will mimic the structure of your values, thus an error in the first element of the array will be stored as errors: { tags: ['Tag not allowed'] }. You can also call setValue with tags[0] as the name, and it will update the proper array element. In your component, the input could be defined like:
import { get } from 'lodash';
...
<label for="tags[0]">{get(touched, 'tags[0]') && get(errors, 'tags[0]')}</label>
<input type=text name="tags[0]" value={get(values, 'tags[0]')}/>
One caveat with this scheme is that you could end up with mixed data types at the tags level. Say the array is empty and the min validation fails as a result. Then the errors object would be { tags: "Please add at least one tag." }, thus the value is a string. But if there is at least one tag, but it's empty, then the errors object would be { tags: ["Tag cannot be empty"] }, a nested array value. Thus, in your components, you'd need to handle both use cases so you don't end up displaying errors.tags thinking it's a string and the error displayed would be [object] if the error was in one of the tags values.
^^^Correct. This is how Yup / Formik deals with arrays. You can also show the tags error if you just check the type first: {typeof errors.tags === 'string' && errors.tags}
Thanks @twelve17 - that was helpful!
Below is an example validation for a new table form where I had to validate for unique table and column names.
The approach is relying on yup's test() method and the context it provides to access the array of columns (via this.parent) and to also throw Yup errors that Formik picks up nicely.
import React, {PropTypes} from 'react';
import yup from 'yup';
import {Formik} from 'formik';
class DataTableEditForm extends React.Component {
/* Other React component boilerplace etc goes here */
/*
* Returns default form values
* */
getDefaultFormValues = () => {
const {tableID} = this.props;
return {
'tableName': '',
'itemID': tableID || '', // Our table ID (if existing table)
'tableColumns': []
};
};
/**
* Returns form validation schema
* @return {*}
* */
getFormValidationSchema = () => {
const {dataTables, tableID} = this.props;
return yup.object().shape({
'tableName': yup.string()
.default('')
.trim()
.test('cannot-begin-with-number', 'Cannot beging with number', value => {
return value.search(/^[0-9]/i) === -1;
})
// Validate our table name uniqueness against external list of tables
.test('unique-name-external-across-external-list', 'Another table has same name', ourTableName => {
// Immutable structure
return dataTables.find(otherTable => {
return otherTable.get('name', '').toLowerCase() === ourTableName.toLowerCase()
&& otherTable.get('itemID') !== tableID;
}) === undefined;
})
.required('Please provide a name')
.min(2, 'Name is too short'),
// An array of table columns
'tableColumns': yup.array().of(
yup.object().shape({
'name': yup.string()
.trim()
.default('')
.min(2, 'Name is too short')
.required('Please provide a name')
// An example with regexp
.test('cannot-begin-with-number', 'Name cannot begin with number', value => {
return value.search(/^[0-9]/i) === -1;
})
})
// Array-level validation to make sure all tableColumns have unique names
// Here we are running the test on the tableColumn object itself (not the 'name' field)
// Make sure that function is not an arrow to retain Yup's `this` context.
.test('columns-no-duplicate-names', 'Name is not unique', function(tableColumn) {
// Skip validation of pre-existing tableColumns
// Or where name is still empty
if(tableColumn.ID !== undefined || tableColumn.name === '') {
return true;
}
const path = this.path; // YUP path to the array item (table column object) being validated
const tableColumns = this.parent; // YUP-way to reference the tableColumns array itself
const columnIdx = tableColumns.indexOf(tableColumn); // Figure out our column index in the array of columns
// Check for another column with duplicate name
const anotherDuplicateColumn = tableColumns.find((columnOther, otherIdx) => {
return otherIdx !== columnIdx && columnOther.name.toLowerCase() === tableColumn.name.toLowerCase();
});
// When duplication detected, throw YUP error (that Formik will pick up)
if(anotherDuplicateColumn) {
throw this.createError({
path: `${path}.name`, // Path to your table column name field that is invalidated
message: 'Looks like a duplicate name'
});
}
return true; // Make sure to return true when all is valid
})
)
.min(1, 'Table needs at least one column')
});
};
render() {
return (
<div>
<Formik
initialValues={this.getDefaultFormValues()}
validationSchema={this.getFormValidationSchema}
>
{/* Your form and inputs etc... */}
</Formik>
</div>
);
}
}
export {DataTableEditForm};
Most helpful comment
Yup is really nice (as is formik), but I have run into validation situations where it'd be nice to be able to use validateSchema as well as a custom validation function.