Formik checkbox won't re-render - reactjs

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)}>

Related

react hook form's getValues() returning undefined

The Question:
How do I access the form's values using react-hook-form's Controller without using setValue() for each input?
The Problem:
I'm creating my own set of reusable components and am trying to use React Hook Form's Controller to manage state, etc. I'm having trouble accessing an input's value and keep getting undefined. However, it will work if I first use setValue().
CodeSandbox example
return (
<form onSubmit={onSubmit}>
<WrapperInput
name="controllerInput"
label="This input uses Controller:"
type="text"
rules={{ required: "You must enter something" }}
defaultValue=""
/>
<button
type="button"
onClick={() => {
// getValues() works if use following setValue()
const testSetVal = setValue("controllerInput", "Hopper", {
shouldValidate: true,
shouldDirty: true
});
// testGetVal returns undefined if setValue() is removed
const testGetVal = getValues("controllerInput");
console.log(testGetVal);
}}
>
GET VALUES
</button>
<button type="submit">Submit</button>
</form>
);
More info:
I don't understand why getValues() is returning undefined. When I view the control object in React dev tools I can see the value is saved. I get undefined on form submission too.
My general approach here is to use an atomic Input.tsx component that will handle an input styling. Then I use a WrapperInput.tsx to turn it into a controlled input using react-hook-fom.
Lift the control to the parent instead and pass it to the reusable component as prop.
// RegistrationForm.tsx
...
const {
setValue,
getValues,
handleSubmit,
formState: { errors },
control,
} = useForm();
...
return (
...
<WrapperInput
control={control} // pass it here
name="controllerInput"
label="This input uses Controller:"
type="text"
rules={{ required: "You must enter something" }}
defaultValue=""
/>
)
// WrapperInput.tsx
const WrapperInput: FC<InputProps> = ({
name,
rules,
label,
onChange,
control, /* use control from parent instead */
defaultValue,
...props
}) => {
return (
<div>
<Controller
control={control}
name={name}
defaultValue={defaultValue}
render={({ field }) => <Input label={label} {...field} />}
/>
</div>
);
};
Codesandbox

react formik disable submit button with form validity

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.

React Formik - app performs unwanted request (submit) after pressing Enter key in the input field

When I write something in the "name" field and press Enter, app performs a request, address bar in the web browser changes to http://localhost:3000/?name=something. But when I just add another input field to the form, app behaviour changes: it doesn't perform request in such case. I would like to ask:
1. Why app behaviour is dependent on the number of input fields?
2. How can I force app to not perform submit (request) when there is
only one field in the form, to act like there would be many fields?
import {Formik} from 'formik';
function App() {
return (
<div className='App'>
<Formik>
{props => (
<form>
<input type="text" name="name"/>
{/*If I uncomment the line below, the app will not perform request after pressing Enter*/}
{/*<input type="text" name="surname"/>*/}
</form>
)}
</Formik>
</div>
);
}
export default App;
I think your validation is just stoping it when you have two fields. HTML form should submit by default when you hit enter. You have an html form.
If you can, I would just remove the html form element and manually control the submit.
<Formik
initialValues={{ name: 'test' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
>
{props => (
<>
<input
type="text"
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.name}
name="name"
/>
{props.errors.name && <div id="feedback">{props.errors.name}</div>}
<button onClick={props.handleSubmit}>Submit</button>
</>
)}
</Formik>
Or just use the formik "Form" component and create a custom event handler for the enter key press.
you need to have a couple of things..
You need to have a validationSchema.
You can do this with yup, or write your own.
Inside the Formik tag, you should then add the following fields
<Formik
initialValues={{ name: "", surname: "" }}
onSubmit={(values) => console.log(values)}
validationSchema={validationSchema}>

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>
);
}
}

How to use custom Input with Formik in React?

I'm trying to use DatePicker within Formik. But when I click DatePicker's date its form value is not changed. Instead, I got this error:
Uncaught TypeError: e.persist is not a function
at Formik._this.handleChange (formik.es6.js:5960)
I shortify code, the code is below
const SomeComponent = () => (
<Formik
render={({
values,
handleSubmit,
handleChange,
setFieldValue
}) => {
return (
<div>
<form onSubmit={handleSubmit}>
<DatePicker
name={'joinedAt'}
value={values['joinedAt']}
onChange={handleChange}
/>
</form>
</div>
)
}}
/>
)
I googled few documents, https://github.com/jaredpalmer/formik/issues/187 and https://github.com/jaredpalmer/formik/issues/86
So I tried like below, but it's not working.
...setFieldValue
<DatePicker
name={'joinedAt'}
value={values['joinedAt']}
onChange={setFieldValue}
/>
I resolve this like
<DatePicker
name={'joinedAt'}
value={values['joinedAt']}
onChange={e => setFieldValue('joinedAt', e)}
/>
Update on 2020-03-08:
You can use e.target.value on setFieldValue's second prop, depends on your custom input design. Thanks to Brandon.
If you have deeper nesting, you should use Formik Field.
Example:
<Formik
validationSchema={schema}
initialValues={initialValues}
onSubmit={(values, actions) => {}}
>
<Field name="colors" component={ColorsEditor} colors={colorArray}/>
</Formik>
const ColorsEditor = ({ field, colors, form, ...props }) => {
return (
<div>
<Button onClick={() => form.setFieldValue('colors', "myValue")}>
</Button>
</div>
)
}
So the Field component already include the form, where live the setFieldValue that you can use where you need. It also include the errors and needed fields for additional customization.
For html primitive input fields handleChange works like a charm, but for custom components, you've to use setFieldValue to change the value imperatively.
onChange={e => setFieldValue('joinedAt', e)}
The accepted answer didn't work for me and didn't show the selected date. It has been almost a year so there might be some changes with the implementation. Though this can be fix by using toDateString() like this
<DatePicker
name={'joinedAt'}
value={values['joinedAt']}
onChange={e => setFieldValue('joinedAt', e.toDateString())}
/>
The Formik solution for this is the useField() method. You may need to create a wrapper for components like DatePicker that you may not have source access to.
The documentation provides an example:
const MyTextField = ({ label, ...props }) => {
const [field, meta, helpers] = useField(props);
return (
<>
<label>
{label}
<input {...field} {...props} />
</label>
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
};
In short, the method takes in the custom props which describe the field. Then you can expand the assigned field value and props in your custom field.
https://github.com/jaredpalmer/formik/issues/692
you need to set value manually if you are using setFieldValue method

Resources