react formik disable submit button with form validity - reactjs

I am using formik library for my react project. I need to disable submit button initially when mandatory fields are empty or have any errors. I checked form status using below code.
const formik = useFormik(false);
<Formik
initialValues={!location.state ? InvoiceInitalValues : location.state}
validationSchema={POValidation} innerRef={formRef}
onSubmit={(form, actions, formikHelper) => {
console.log(form);
console.log(formRef);
setTimeout(() => {
saveInvoice(form, actions, formikHelper);
actions.setSubmitting(false);
}, 1000);
}}
>
{({
handleSubmit,
handleChange,
setFieldValue,
values,
handleReset,
errors,
resetForm, isSubmitting
})
I console logged form status using but form is always 'isValid: True'. Please check below image.
I printed it in the html also. but always isValid is True. I need to this for disable my button
printed status
<p> {formik.isValid=== true ? "True" : "False"} </p>
I tried below code to disable my button
<Button className="btn btn-primary mr-1" type="submit" disabled={!formik.isValid}>
Submit
Why formik form validity is always true, even mandatory fields are empty. I spent more time to solve this. still could not fix this. Anyone know to fix this

You created form using Formik component, but you are checking isValid on another formik instance created through useFormik hook. That's not right.
You should use either <Formik> or useFormik.
As you have written code using Formik component. Here is how you access isValid property:
<Formik
initialValues={!location.state ? InvoiceInitalValues : location.state}
// ...
>
{({
values,
handleChange,
// ...
isValid // HERE
}) => (
<form onSubmit={handleSubmit}>
<input
...
Here is a codesandbox.

Related

Formik with FetchBaseQuery

I have a form built with Formik. The form has an initial input box in which user is supposed to enter his username, if the username is valid then the API will return the subsequent details for the user which I need to pre-fill further in the form.
Is there a better way to handle it in Formik.
I explored and find out I can use initialValues but I need to initialise it a bit later.
The form looks something like the following, but has a lot more details to display
Also, if the user doesn't have a username then also we can let the user enter details manually & click on submit.
I tried the following, but now I believe it is not an optimal solution to the problem, can you please help me how to take care of such use cases
const response = useQuery(username,{skip});
const userData: any = response?.data;
const formikRef = useRef<FormikProps<FormikValues>>(null);
React.useEffect(() => {
if (userData) {
formikRef.current?.setFieldValue("name", userData.name);
formikRef.current?.setFieldValue("detail1",userData.detail1);
formikRef.current?.setFieldValue("detail2",userData.detail2);
}
}, [agreementLineData]);
return (
<Formik
innerRef={formikRef}
initialValues={{
username: "",
name: "",
detail1: "",
detail2: ""
}}
onSubmit={(values) => {})
{({ values, submitForm, setFieldValue }) => {
return (
<Form>
<FormField label="Username">
<Input value={values.username}/>
<Button variant="primary">Validate</Button>
</FormField>
<FormField label="Name">
<Input value={values.name} onChange={(e) => {}}/>
</FormField>
<FormField label="Details 1">
<Input value={values.detail1} disabled={true}/>
</FormField>
<FormField label="Details 2">
<Input value={values.detail2} disabled={true}/>
</FormField>
</Form>
</Formik>
)
It is better to use initialValues of Formik to populate your form's input rather than having each one use setFieldValue.
All you need to do for the above is to add enableReinitialize prop.
It will re-initialize the form whenever your initialValues are updated. This way your values would be populated properly.
Just play with initialValues and you should be good to go.

Passing value to hidden input from dropdown menu in react

I have react-select dropdown menu and hidden input which I pass to form when submiting...
using useState hook I created variable which tracks changes to react-select dropdown menu.
Hidden input has this variable as value also. I thought this would be enough.
But when I submit the form, console. log shows me that value of input is empty despite that variable that was selected from dropdown menu is actually updated.
I mean variable that I have chosen console logs some value, but hidden input thinks that it is still empty.
Does it means I have to rerender manually page each time I change that variable so input gets it's new value using useEffect ? Which is bad solution for me, I don't like it, thought it would be done automatically.
Or instead of useState I must create and use variable via Redux ? Which I also don't like, use redux for such small thing fills overcomplicated.
Isn't there any nice elegant solution ? :)
import { useForm } from 'react-hook-form';
const [someVar,setSomeVar]=useState('');
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ mode: 'onBlur' });
const handleFormSubmit = (data) => {
console.error('success');
};
const handleErrors = (errors) => {
console.error(errors);
console.log(document.getElementsByName('hiddenInput')[0].value);
};
const options = {
hiddenInput: {
required: t('hiddenInput is required'),
},
};
.......
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Select
options='...some options list'
onChange={(value) => setSomeVar(value)}
/>
<input
name='hiddenInput'
value={someVar}
{...register('hiddenInput', options.hiddenInput)}
/>
<button>submit</button>
</form>
UPDATED
Its because getElementsByName returns an array of elements.
You probably want
document.getElementsByName('hiddenInput')[0].value
I should add that really you should use a ref attached to the input and not access it via the base DOM API.
const hiddenRef = useRef(null)
// ...
cosnt handleSubmit =(e)=>{
console.log(hiddenRef.current.value);
}
// ...
<input
name='hiddenInput'
value={someVar}
ref={hiddenRef}
/>
However as you are using react-hook-form you need to be interacting with its state store so the library knows the value.
const {
register,
handleSubmit,
formState: { errors },
setValue
} = useForm({ mode: 'onBlur' });
// ...
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Select
options='...some options list'
onChange={(value) => setValue('hiddenInput', value)}
/>
<input
name='hiddenInput'
{...register('hiddenInput', options.hiddenInput)}
/>
<button>submit</button>
</form>
You can remove const [someVar,setSomeVar]=useState('');
However, this hidden input is not really necessary as you mention in comments. You just need to bind the dropdown to react hook form.
// controller is imported from react hook form
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Controller
control={control} // THIS IS FROM useForm return
name="yourDropdown"
rules={{required: true}}
render={({
field: { onChange, value, name, ref }
}) => (
<Select
options={options}
inputRef={ref}
value={options.find(c => c.value === value)}
onChange={val => onChange(val.value)}
/>
)}
/>
<button>submit</button>
</form>

Update Formik's state from a button onClick

I have a Formik form and when I click a button I want to update the form's state.
function updateForm(){
//Update a product in the form's state
}
<Button onClick={updateForm}>Update Product</>
<Formik
initialValues={{ products: {} }}
>
{({values}) = (
)}
</Formik>
I tried to update the initialValues but that will reset any current changes in the form's state.
I could copy the Form's state into a local setState and loop that back into the initialValues but that feels overkill, as Formik does that already.
I thought useFormik might work, but the docs don't expand how this might work...?
Given the helpful comments from #aditaya81070, I eventually landed on this solution...
I also had to refactor my solution a little, because I had two Forms on the page and in order to use setFieldValue I had to nest both Forms within the <Formik> element, so they could communicate easily (they can still have their own state separately). But don't nest them within themselves as that's not allowed.
So this is fine:
<Formik>
<Form />
<Form />
</Formik>
but this is not...
<Formik>
<Form>
<Form />
</Form>
</Formik>
Then I just pass in the setFieldValue into the callback of the button...easy :)
function updateForm(setFieldValue){
//Update a product in the form's state
setFieldValue('product.price'), 1.50);
}
<Formik
initialValues={{ products: {} }}
>
{({values, setFieldValue, validateForm}) = (
<button onClick={() =>
//This is the import bit, to get the context.
updateForm(setFieldValue)
}>
Update Product
<button/>
)}
</Formik>

How to make invisible react-google-recaptcha, Formik and yup work together?

I'm trying to make invisible react-google-recaptcha, Formik and yup to work together. The documentation says we should call recaptchaRef.current.execute() on form submission, but if we use Formik and yup together, it will trigger the submission logic only after all fields passed the validation schema.
Basically, we need to call the execute method, update the recaptcha value and submit the form with the same trigger event. My problem is exactly that: I'm having to use two events (one for the execute method and update the recaptcha + one to submit the form).
Check this sandbox: https://codesandbox.io/s/keen-mahavira-ont8z?file=/src/App.js
As you can see, the form is submitted only with the second click in the submit button...
With Formik, there are some ways to do background work for your form's. This basically could be achieved with handleChange or handleBlur props being passed to the form component.
For instance, I am sure you would have other inputs in your form elements and not just a captcha (if it's just a captcha in the form, then do let me know! - this can also be solved)
So when you have other elements, you can ensure to use some of the Formik's API to handle the automatic trigger:
handleBlur event to trigger the ReCaptcha
isSubmitting to control the submit button's state
setSubmitting event to manipulate the button's state
As I see, there are a lot of ways to handle this through their API's: https://formik.org/docs/api/formik
The way I tried to achieve it is by adding a listener for onBlur on all fields and then checking if reCaptcha value is present or not. Based on that I trigger the execute the captcha and ensure to set the submitting value as true:
const handleBlur = (e) => {
console.log("$$$$", props.isSubmitting);
if (!props.values.recaptcha) {
this._reCaptchaRef.current.execute();
props.setSubmitting(true);
}
props.handleBlur(e);
};
Here is the CodeSandbox Link: https://codesandbox.io/s/silly-saha-qq7hg?file=/src/App.js
This shows the working model of handling onBlur of a field and triggering it in the background. If you notice, you can also disable and enable the submit button using isSubmitting and setSubmitting.
Also setting validateOnChange={false} and validateOnBlur={false}, because there is no need to validate on change or blur for captcha.
Pasting code here just in case for you to glance:
import React, { Component, createRef } from "react";
import ReCAPTCHA from "react-google-recaptcha";
import { Formik } from "formik";
import * as yup from "yup";
const TEST_SITE_KEY = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI";
export default class MyForm extends Component {
constructor(props) {
super(props);
this._validationSchema = yup.object().shape({
recaptcha: yup.string().required(),
name: yup.string().required(),
address: yup.string().required()
});
this._initialValues = { recaptcha: "", name: "", address: "" };
this._reCaptchaRef = createRef();
}
render() {
return (
<Formik
validationSchema={this._validationSchema}
initialValues={this._initialValues}
validateOnChange={false}
validateOnBlur={false}
onSubmit={(values) => console.log(values)}
>
{(props) => {
const handleBlur = (e) => {
console.log("$$$$", props.isSubmitting);
if (!props.values.recaptcha) {
this._reCaptchaRef.current.execute();
props.setSubmitting(true);
}
props.handleBlur(e);
};
return (
<form onSubmit={props.handleSubmit}>
<label>Name: </label>
<input
type="text"
onChange={props.handleChange}
value={props.values.name}
name="name"
onBlur={handleBlur}
/>
<label>Address: </label>
<input
type="text"
onChange={props.handleChange}
value={props.values.address}
name="address"
onBlur={handleBlur}
/>
<ReCAPTCHA
ref={this._reCaptchaRef}
sitekey={TEST_SITE_KEY}
onChange={(value) => {
console.log("$$$$", props.isSubmitting, value);
props.setFieldValue("recaptcha", value);
props.setSubmitting(false);
}}
size="invisible"
/>
<button type="submit" disabled={props.isSubmitting}>
SUBMIT
</button>
{props.errors.name && <div>{props.errors.name}</div>}
</form>
);
}}
</Formik>
);
}
}

Formik checkbox won't re-render

I am using Formik library and have a simple form with one checkbox that I would like to submit on change:
<Formik
initialValues={{ toggle: false }}
validateOnChange={false}
validateOnBlur={false}
onSubmit={(values, { validateForm }) => {
validateForm().then(_errors => {
console.log(values);
});
}}
>
{({ values, handleChange, handleSubmit }) => (
<div>
<form onChange={handleSubmit}>
<label>
Toggle
<input
name="toggle"
type="checkbox"
checked={values.toggle}
onChange={handleChange}
/>
</label>
</form>
</div>
)}
</Formik>
For some reason, it looks like the input is not being re-render after every click, only after every second click. As a result checkbox is not being update - you have to click twice for it to change (onChange event only fires every second time)
I can force it to re-render by adding key but it's a hack.
Here's the sandbox: https://codesandbox.io/s/formik-checkbox-issue-ew65e
Your problem is that you are trying to submit the form at every change.
Idealy you should debounce this behaviour (using lodash maybe ?) :
import _ from 'lodash'
<form onChange={_.debounce(handleSubmit, 300)}>
Alternatively, if you don't use lodash, you can make the call to handleSubmit asynchronous by wrapping it in a setTimeout like this :
<form onChange={() => setTimeout(handleSubmit, 0)}>

Resources