Formik + Yup: How to immediately validate form before submit? - reactjs

I want to show field errors when form mounted. Not after submit.
Yup:
const validation = Yup.object().shape({
field: Yup.string().required('Required')
});
Formik:
<Formik
initialValues={initialValues}
validationSchema={validation}
validateOnMount
>
...
</Formik>
Thanks for help!

The simple answer is
Pass initialTouched to Formik which will be an object with key of the fields you want to show the error message and the value true for those keys.
e.g.
<Formik
initialValues={initialValues}
initialTouched={{
field: true,
}}
validationSchema={validation}
validateOnMount
>
...
</Formik>
But there is alot of ways to do this, but you can create an component that call validateForm on the initial render
const ValidateOnMount = () => {
const { validateForm } = useFormikContext()
React.useEffect(() => {
validateForm()
}, [])
// the return doesn't matter, it can return anything you want
return <></>
}
You can also do the same using validateOnMount and setting initialTouched to true on all fields you want to display the error message (maybe you only want to show certain field's error message on the initial mount).
<Formik
validateOnMount
initialValues={initialValues}
validationSchema={validation}
initialTouched={{
field: true
}}
{...rest}
>
</Formik>
Where initialTouched should be an object that have keys to all fields that you want to validate that are in initialValues but with the value true, which means you already touched that field.
Another way to do this is only passing validateOnMount to Formik and display any error message without checking for touched[name].
If you use ErrorMessage from formik, it won't work because it checks for touched[name] === true to display the message, so you need to create your own way of displaying the error, but only checking for errors[name].

you can use errors and touched props, like this
<Formik
initialValues={initialValues}
validationSchema={validation}
validateOnMount
>
{props => {
const { errors, touched } = props
return (
<Form>
...
{errors.field && touched.field ? <Error>{errors.field}</Error> : null}
...
</Form>
)
}
</Formik>

There's a prop called isInitialValid.
Setting it to false solved my problem :)

Related

React using Formik does not clear the data value in Material UI form

I'm using formik.resetForm() to remove values from text fields in a form after submitting the data.
...
const handleSubmitProduct = async (values: Object, resetForm: any) => {
... code to handle my form data ...
resetForm()
if (response.ok) {
console.debug(response.status)
} else {
console.error(response)
}
}
const validate = (values: Object) => {
const errors: any = {}
if (!values.product_name) {
errors.product_name = "Include name"
}
return errors
}
... initialValues defined ...
const formik = useFormik({
initialValues: initialValues,
validate,
onSubmit: (values: Object, { resetForm }) => {
console.debug(JSON.stringify(values, null, 2))
handleSubmitProduct(values, resetForm)
},
})
return (
<FormLabel>Display name</FormLabel>
<TextField
onChange={formik.handleChange}
id="product_name"
onBlur={formik.handleBlur}
error={formik.touched.product_name && Boolean(formik.errors.product_name)}
helperText={formik.touched.product_name && formik.errors.product_name}
/>
<Button onClick={() => formik.handleSubmit()} variant="contained">
Submit
</Button>
)
I know there are many other questions like this but mine is different where I know the underlying Formik resources for values, errors, touched have been cleared but the values are still present in the text boxes.
The issue is I know the underlying Formik objects are cleared because after I submit, the validation triggers and prompts me like there is no value in the text field.
I've tried
resetForm({values: {initialValues}}) has the same result
resetForm(initialValues) has the same result
Use action.resetForm({values: {initialValues}}) in the onSubmit() which same result
https://codesandbox.io/s/mui-formik-fr93hm?file=/src/MyComponent.js but this approach uses the <Formik /> as opposed to useFormik which would change up my entire page but I'm in process to try anyway
I think the problem is that value of TextField is not value of formik. so the TextField is not controlled and by chaning value of formik it won't change.
assigning value of formik to it will do what you want
value={formik.values.firstName}
like this :
<TextField
onChange={formik.handleChange}
id="product_name"
value={formik.values.firstName}
onBlur={formik.handleBlur}
error={formik.touched.product_name && Boolean(formik.errors.product_name)}
helperText={formik.touched.product_name && formik.errors.product_name}
/>

Validating a child input type file with react-hook-form and Yup

I'm creating a form with a file upload with help of react-hook-form and Yup. I am trying to use the register method in my child component. When passing register as a prop (destructured in curly braces) the validation and submiting doesn't work. You can always submit the form and the submitted file object is empty.
Here's a sandbox link.
There are several of problems with your code.
1- register method returns an object with these properties:
{
onChange: function(){},
onBlur:function{},
ref: function(){}
}
when you define your input like this:
<input
{...register('photo')}
...
onChange={(event) => /*something*/}
/>
actually you are overrding the onChange method which has returned from register method and react-hook-form couldn't recognize the field change event. The solution to have your own onChange alongside with react-hook-form's onChange could be something like this:
const MyComp = ()=> {
const {onChange, ...registerParams} = register('photo');
...
return (
...
<input
{...params}
...
onChange={(event) => {
// do whatever you want
onChange(event)
}}
/>
);
}
2- When you delete the photo, you are just updating your local state, and you don't update photo field, so react-hook-form doesn't realize any change in your local state.
the problems in your ImageOne component could be solved by change it like this:
function ImageOne({ register, errors }) {
const [selectedImage, setSelectedImage] = useState(null);
const { onChange, ...params } = register("photo");
return (
...
<Button
className="delete"
onClick={() => {
setSelectedImage(null);
//Make react-hook-form aware of changing photo state
onChange({ target: { name: "photo", value: [] } });
}}
>
...
<input
//name="photo"
{...params}
type="file"
accept="image/*"
id="single"
onChange={(event) => {
setSelectedImage(event.target.files[0]);
onChange(event); // calling onChange returned from register
}}
/>
...
);
}
3- Since your input type is file so, the value of your photo field has length property that you can use it to handle your validation like this:
const schema = yup.object().shape({
photo: yup
.mixed()
.test("required", "photo is required", value => value.length > 0)
.test("fileSize", "File Size is too large", (value) => {
return value.length && value[0].size <= 5242880;
})
.test("fileType", "Unsupported File Format", (value) =>{
return value.length && ["image/jpeg", "image/png", "image/jpg"].includes(value[0].type)
}
)
});
Here is the full edited version of your file.

Using Yup to validate radiobox or checkbox value

I am using two library with is Formik and Yup. And trying to validate by the value stored on the radio box(check box as well) but seems like I cant find anything on google where I can get the actual value to check. I can use the name value to validate but is there a way to check validation by looking at the value? I will provide the example below.
const initialValues = {
A: "",
B: "",
C: [],
};
const onSubmit = (values) => {
console.log(values);
};
const validationSchema = Yup.object({
A: Yup.string().required("Required"),
B: Yup.string().required("Required"),
C: Yup.array().min(1, "Must be selected"),
});
and here is the return for react
return (
<>
<Formik
enableReinitialize={true}
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
render={(props) => <TreatmentView {...props} data={data} />}
/>
</>
);
And this is the rendered view below
I want to store that logic on the validationSchema. So again, I want to add a validation where checkbox with name "C" with value "4" can only be checked when radio button of name "B" with value "3" is selected. I did looked at the doc for the YUP and tried using the .value but seems like its not working..
Thank You!

getting setstate to cause a rerender every time in a useEffect block

I've created this codesandbox example and here is the code:
import React, { ReactNode, useState } from "react";
import { Formik, FormikConfig, FormikProps, Form, FormikErrors } from "formik";
import { useEffect } from "react";
import { scrollToValidationError } from "./scrollToValidationError";
// const isEmpty = (a: unknown): boolean =>
// typeof a === "object" && Object.keys(a).length > 0;
export type FormContainerProps<V> = {
render({
values,
errors,
invalid,
submitCount,
isSubmitting
}: {
values: V;
invalid: boolean;
errors: FormikErrors<V>;
submitCount: number;
isSubmitting: boolean;
}): ReactNode;
additionalContent?: ReactNode;
nextButtonText?: string;
} & Pick<FormikConfig<V>, "initialValues" | "validate"> &
Partial<Pick<FormikConfig<V>, "onSubmit">>;
export const FormContainer = function FormContainer<V>({
initialValues,
additionalContent,
validate,
render,
...rest
}: FormContainerProps<V>) {
const [hasValidationError, setHasValidationError] = useState(false);
useEffect(() => {
if (!hasValidationError) {
return;
}
scrollToValidationError();
}, [hasValidationError]);
return (
<>
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={async (values, { validateForm }) => {}}
>
{({
isSubmitting,
submitCount,
isValid,
errors,
values
}: FormikProps<V>) => {
const invalid = !isValid;
if (submitCount > 0 && invalid) {
setHasValidationError(true);
}
return (
<>
<div data-selector="validation-summary">Validation Summary</div>
<Form>
<div>
<div>
{render({
values,
errors,
isSubmitting,
invalid,
submitCount
})}
</div>
<div>
<button type="submit">SUBMIT</button>
</div>
</div>
</Form>
</>
);
}}
</Formik>
</>
);
};
Basically I am calling setHasValidationError(true) which breaks the dependency watcher on useEffect
useEffect(() => {
if (!hasValidationError) {
return;
}
scrollToValidationError();
setTimeout(() => setHasValidationError(false));
}, [hasValidationError]);
But if this is a form with multiple errors then I want to trigger the useEffect every time but I don't know when to reset it to false or if there is a better way.
In order to scroll to the first error field upon clicking on submit then you can do the following:
Write a custom component (eg: FocuseabelField) that renders formik field which also handles automatic scroll to element and focus on error input
Use Formik's innerRef
Just use the formik's isSubmitting and errors to handle logic for scrolling and focussing
FocuseabelField custom component
const FocuseabelField: any = props => {
const elementRef = useRef<HTMLDivElement>();
if (
props.isSubmitting &&
elementRef.current !== undefined &&
props.errors.hasOwnProperty(props.name)
) {
elementRef.current.scrollIntoView();
elementRef.current.focus();
}
return <Field {...props} innerRef={elementRef} />;
};
Usage
<FocuseabelField
errors={errors}
isSubmitting={isSubmitting}
name="name"
placeholder="enter name"
className={errors && errors.name ? "input error" : "input"}
/>
I have taken your code and have commented the stuff like scrollToValidationerror.ts, dom.ts, wait.ts, useState(hasValidationError), useEffect etc.
Simplified working copy of the code is here. I have used 2 fields to demonstrate multiple errors & auto scroll & focus to the error field:
https://codesandbox.io/s/usemachine-typescript-problems-tns0c?file=/src/components/Home/index.tsx
When the forms gets bigger it becomes complicated to manage so its good to consider outsourcing the form validation part and use libraries such as yup and maintain a validation schema and pass it on to formik.
Have a look at the formik docs for examples.
What about creating an Object with keys for each form field? That way you can maintain a specific form validation error for each input and use that Object in the useEffect second parameter, it will make sure it's triggered for each form error update
To answer your original question,
useEffect does reference check for its dependencies, so you could use Object instead of value. So something like this.
const [hasValidationError, setHasValidationError] = useState({value: false});
useEffect(() => {
if (!hasValidationError.value) {
return;
}
scrollToValidationError();
}, [hasValidationError]);
setHasValidationError({value: true});
But in regards to Formik usage, I highly recommend you follow what #gdh pointed out.
I don't understand the need of effects here.
Why don't you call the method directly instead of using hooks?
You can avoid 2 re-renders by doing that, and the component can also be stateless!
...
}: FormikProps<V>) => {
const invalid = !isValid;
if (submitCount > 0 && invalid) {
scrollToValidationError();
}
...

Display an alert if the form has errors on submit in react-final-form

I'm using react-final-form and I would like to display an alert if a user tries to submit a form with errors. But I don't know how to do it. I've made a function for form onSubmit like this:
onSubmit={event => {
event.preventDefault();
//How to check if the form has any validation errors?
if () {
alert("Errors");
} else {
handleSubmit();
}
}}
How can I check in it if there are any validation errors? Here is my codesandbox.
There are lots of possible ways to do it. Here's one:
<form
onSubmit={event => {
if (invalid) {
alert(JSON.stringify(errors, undefined, 2));
}
handleSubmit(event);
}}
>
...fields
</form>
You can do it in onSubmit method by checking every validation on form values.
Instead, use validate prop which is mentioned in doc:
validate?: (value: ?any, allValues: Object, meta: ?FieldState) => ?any
This will let you define rules on individual fields and validate value before submitting a call.
I hope this will help you.

Resources