Open pumanitro opened 5 years ago
Having same issue here. We have range inputs, where numbers should not revert range (e.g. min value should not be more than max and vice versa). Lacking this feature forces us to do some complicated solutions like top level validation which is really hard to follow with a huge form. I believe you pass the whole form object as a second parameter into field validation function then I can decide whatever I want to use it for.
I agree. If the formik bag is available to a <Field>
's children, why would it not be available to validate
as well?
This missing feature is causing a lot of issues for me as well, especially with related fields like password and re-enter password which need cross-validation. I would prefer to use field-level validation for this but it's impossible unless you can get access to up-to-date values. I tried to pass the current form object into my validation function but the values are one render cycle behind and therefore useless. Any update on making this possible?
We are also finding it troublesome for implementing cross field validation. It would be great, when using the useField-Hook, FieldMetaProps would not only include the value "plucked out from values", but also the values themselves. Like that, cross-field validation would be easy to implement.
I would also like support for something like this. Form level validation would work if I was able to tell if fields had been touched or not.
Seems reasonable, can this be done with useEffect though?
I am not sure why/where you would use useEffect. I thought it should be easy to not "pluck out from values" the one value of the respective field, but instead give all those values to the field, isn't it?
Yeah that’s fine by me
Great! Are you saying that you will be working on it?
I’m running into the same issue with range fields. In my case, validation props like min/max are strings that refer to other values in the formik values object. So my range inputs are just components that wrap formik inputs.
I managed to create a workaround by using
When the validation callback is called I iterate through each yup validator and provide it the values object.
I tried to use the useField validate prop, but there wasn’t any point because I had to pass in the formik values object. I got that from useFormikContext. This resulted in duplicated rendering and terrible performance.
I have this same issue. In my form I have a date field, and two other time fields in which their time must be after 'now', if the selected day is today. So I'm having trouble validating these time fields dynamically when the date is changed.
I tried something like @Menardi mentioned above, using the useField
hook, and the useFormikContext
to get the value of the date field, and use it inside the validate of the validate method. But the performance loss was huge, and for some reason I didn't got the data flow right, the validate method passed to the useField
hook, appears to be being called before the updated date value, comes from the useFormiKContext
hook :/
I successfully managed to get my validation to work with a little hack:
const { validateField, values } = useFormikContext();
const fieldValue1 = values?.fieldValue1;
const fieldValue2 = values?.fieldValue2;
const validate = useCallback(
value => {
// validation logic
},
[fieldValue1, fieldValue2]
);
...
const [field, meta, helpers] = useField({
name: fieldName,
validate
});
useEffect(() => {
const timeoutId = setTimeout(() => validateField(fieldName), 50);
return () => clearTimeout(timeoutId);
}, [fieldName, validate, validateField]);
By the time the validate
method of invoked by formik, the other field's values weren't propagated yet to the component updating the validate method, but with this useEffect
I re-invoke the validation of the field once, and it did the trick.
Is it a hack? Yes. Does it work? Yes.
I managed to reference other fields with custom validation using a ref. Full example below -- don't let the length of the example throw you off, it's a clean and simple solution.
Relevant code: const fieldRef = useRef();
, <Field>{({ form, field }) => <input ref={ref} {...field} />}</Field>
, <Field validation={val => myValidationFn(val)} />
and myValidationFn()
TLDR: Any extra field I want to reference in my validation is wrapped within <Field></Field>
and has a ref added to the child input. Ref value is called in a custom validation function via the validate prop: <Field validate={val => customFn(val)} />
-- yup validations are preserved and ran along side your custom validation function.
import React, { useRef } from 'react';
import { Formik, Form, Field } from 'formik';
import MyFeedbackComponent from '/path/to/MyFeedbackComponent';
import * as yup from 'yup';
const MyComponent = props => {
const fieldRef = useRef();
const defaults = {
myReferenceField: null,
myOtherField: null,
};
const schema = yup.object().shape({
myReferenceField: yup.number().required('Reference field is required.'),
myOtherField: yup.number().required('My other field is required.'),
});
const handleSubmit = (values, resetForm, setSubmitting) => {
const { myReferenceField, myOtherField } = values;
// Your submit payload, API call, form/submit reset etc. here...
};
const myValidationFn = otherFieldVal => {
const limit = 100;
const referenceVal = fieldRef.current.value;
const errorMsg = `Total must be under ${limit}.`;
if (referenceVal) {
// Your custom condition and error.
const underLimit = otherFieldVal * referenceVal < limit;
return !underLimit && errorMsg;
}
return true;
};
return (
<Formik
initialValues={defaults}
enableReinitialize={true}
validationSchema={schema}
onSubmit={(values, {resetForm, setSubmitting}) => (
handleSubmit(values, resetForm, setSubmitting)
)}>
{form => {
return (
<Form>
<label>
/* This field is written this way so we can attach a ref to it.
* That way the most recent state value for this field is
* available in our custom validation function. */
<Field type="number" name="myReferenceField">
{({form, field}) => <input ref={fieldRef} {...field} />}
</Field>
<MyFeedbackComponent name="myReferenceField" />
</label>
<label>
// This field will call a custom validation function that will
// reference fieldRef's value.
<Field
type="number"
name="myOtherField"
validate={otherFieldVal => myValidationFn(otherFieldVal} />
<MyFeedbackComponent name="myOtherField" />
</label>
<button type="submit" disabled={form.isSubmitting}>
Submit
</button>
</Form>
)};
}
</Formik>
);
};
Any news on this?
I agree that the values should be passed as a second argument to the validate function. Here's a quick workaround in the meantime:
const {
values,
validateField
}: FormikProps<YourFormData> = useFormikContext();
useEffect(() => {
validateField("amount");
}, [values.amountType, validateField]);
It also works to provide the values to your validation function at the time of render, i.e have your validation function derived from a returned function:
export const validateLaunchDateIsBeforePreLaunch = ( values ) => {
return (launchDate) => {
const preLaunchDate = values.preLaunch;
const myIsBefore = (preLaunchDate && isBefore(new Date(launchDate), new Date(preLaunchDate)));
if(myIsBefore) {
return "Launch date is before pre launch date";
}
};
};
const { values } = useFormikContext();
return (
<Field
name="launchDate"
validate={validateLaunchDateisBeforePreLaunch(values)}
.../>
)}
Not sure how to achieve this if you're using a class component instead of functional.
@boroth you can use connect()
from formik
to connect your class-based child of Formik to state, then use componentDidUpdate
which is basically the same "lifecycle hook" as useEffect.
class MyClassComponent {
componentDidUpdate(props) {
console.log('validate your thing here');
}
}
const MyConnectedClassComponent = connect(MyClassComponent);
const MyForm = () => {
return <Formik {...formikProps}>
<MyConnectedClassComponent />
</Formik>;
};
My plan, if TypeScript enables it, is eventually to support:
const MyDependentField = () => <Field
name="myField"
include={state => ({ otherFieldValue: state.values.otherField })}
validate={(value, { otherFieldValue }) => value === otherFieldValue ? "Fields cannot be the same" : ""}
/>
However, that will be dependent on #1334 and #3089 because we need to resolve types before adding this functionality, as well as optimize subscriptions to Formik's API so this isn't super expensive.
@johnrom, I could kiss you right now. Been jumping through hoops trying to get this to work and I had totally missed the connect()
method in the documentation. I'm still only a few months into learning React, so it completely slipped by me. Thanks! I'll definitely need to spend more time learning about Redux and React lifecycle methods. I really appreciate it!
Formik's connect
is different from redux's connect, but it does basically the same thing specific to connecting to Formik's state.
To fix it here, first I created a useState, which will store the variable that causes dependency. In my case, the card's flag is required to validate the card number.
const [brandName, setBrandName] = useState('')
Then, I transformed the onChange function into an asynchronous function. Thus, when the card brand changes, we execute the validateField of the card number in parallel. That way, it doesn't need a timeout or anything.
const handleOnChange = async (field: string, value: string) => {
// The cardNumber field has a dependency of brand name. So, needed a another validation:
if (field === 'paymentCompanyCode' && formik.values.cardNumber !== '') {
setBrandName(value)
await validateField('cardNumber')
}
setFieldValue(field, value);
setFieldTouched(field, true, false);
return validateForm({ ...formik.values, [field]: value });
};
Recently I have moved to https://github.com/final-form/react-final-form because they support validate function with following signature:
( value: FieldValue, allValues: object, meta?: FieldState<FieldValue>) => any
allValues
gives me access to the whole state without precedence/reference problems (which hacks like useEffect
with validateField
can solve, but it doesn't fit right with me ).
It would be great if @johnrom 's proposal would be accepted ( also in useField
hook). That would bring me back to using formik (which I personally prefer due to better documentation and typing) :)
This seemed to work for me using Yup.ref
, hopefully it's relevant?
<Formik
initialValues={{ newPassword: '', confirmNewPassword: '' }}
validationSchema={Yup.object().shape({
newPassword: Yup.string()
.required('New Password is required.')
.min(8, 'New Password must be at least 8 characters long.')
.matches(/\d/, 'New Password must contain at least 1 number.')
.matches(
/[A-Z]/,
'New Password must contain at least 1 uppercase letter.',
),
confirmNewPassword: Yup.string().oneOf(
[Yup.ref('newPassword')],
'Passwords must match',
),
})}
Any updates on this requested logic. On field level validation, having the ability to validate a field's value based on another field's value using the most up to date values object after the other field's value has been changed would be super helpful.
🚀 Feature request
Current Behavior
<Field validate={(fieldValue) => ... } />
For now field level validaiton funciton gives us only field value.Desired Behavior
<Field validate={(fieldValue, formikBag) => ... } />
I want to have access to formikBag inside of field level validation.Suggested Solution
Pass additional argument with formikBag to validate callback function.
Who does this impact? Who is this for?
I want to have possibility to access other formik values to create dependent validation. E.g. I have Field1 and Field2. Field2 should be invalid if Field1 is true and Field2 have some values inside. Form is reused and you can compound it from smallest reuseable pieces. It means sometimes you can have Field2 defined or undefined, that is why I don't want to use for this case global <Formik validation method.
Describe alternatives you've considered
Global Formik validation method - no, bcs of -> I want to see validation on component Blur, and want to compound form from smallest Fields element that can be included or not dynamically.
Field Field nesting just to have form values inside of second Field validation
No. Just have a look at this. Unnecessary component just to retrieve values I should have access to.