Formik with FetchBaseQuery - reactjs

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.

Related

Formik issue: Unable to edit text field in simple form in React app

I am unable to enter any text into a text input field within a plain html form that uses formik to handle changes to the field and to handle submit.
See the issue in action here:
https://formik-plain-form-issue.netlify.app/
I have encountered this issue while working on an app that uses Formik in combination with Redux, Bootstrap and Reactstrap, so I thought the issue may be related to these other libraries. However, a simplified version with a plain form and no state management is also showing a similar behaviour.
Here I am providing the code for the simplified version.
PlainForm component code:
// This is a React component
import { useFormik } from 'formik';
import './PlainForm.css';
const PlainForm = () => {
const formik = useFormik({
initialValues: {
name: '',
},
onSubmit: values => {
alert(`Plain form submitted with name: ${values.name}`);
},
});
return (
<div className='plain-form-container'>
<h1>Plain Form</h1>
<form
className='baseForm'
onSubmit={formik.handleSubmit}
noValidate
>
<label htmlFor='plain-form-name'>Name:</label>
<input
type='text'
id='plain-form-name'
className='nameField'
placeholder='Enter your name here'
value={formik.values.name}
onChange={formik.handleChange}
/>
<button type='submit'>Submit</button>
</form>
</div>
);
};
export default PlainForm;
You may see the full code for the test app here:
https://github.com/freenrg/Formik-Plain-Form-Issue
As pointed out by #shadow-lad in the comments, Formik needs the form field to have id and name matching the key of the property defined for that field in initialValues.
See https://formik.org/docs/tutorial
If you look carefully at our new code, you’ll notice some patterns and symmetry forming.
We reuse the same exact change handler function handleChange for each
HTML input
We pass an id and name HTML attribute that matches the property we defined in initialValues
We access the field’s value
using the same name (email -> formik.values.email)
Therefore, the code should be:
// This is a React component
import { useFormik } from 'formik';
import './PlainForm.css';
const PlainForm = () => {
const formik = useFormik({
initialValues: {
name: '',
},
onSubmit: values => {
alert(`Plain form submitted with name: ${values.name}`);
},
});
return (
<div className='plain-form-container'>
<h1>Plain Form</h1>
<form
className='baseForm'
onSubmit={formik.handleSubmit}
noValidate
>
<label htmlFor='plain-form-name'>Name:</label>
<input
type='text'
id='name'
name='name'
className='nameField'
placeholder='Enter your name here'
value={formik.values.name}
onChange={formik.handleChange}
/>
<button type='submit'>Submit</button>
</form>
</div>
);
};
export default PlainForm;
I have confirmed this works.

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

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

How to handle Formik's `handleChange` prop?

I get it that Field has an onChange attribute where you can pass the own Formik onChange prop from here: https://jaredpalmer.com/formik/docs/api/formik#handlechange-e-reactchangeevent-any-void.
However, I am struggling to understand where these value[key] is passed, so I can handle the data passed in the form. Found in withFormik(): How to use handleChange that I can pass two callbacks to Formik's onChange prop, but I wonder if there is a better way to handle this.
edit after comments from folks that replied, thanks for that:
My code using these 2 callbacks in the onChange prop in Field:
export default function FormikForm() {
const onSubmitHandler = (formValues, actions) => {
console.log(formValues);
console.log(actions);
};
const onChangeHandler = (e) => {
console.log(e.target.value);
};
return (
<div>
<h1>This is the Formik Form</h1>
<Formik
initialValues={{
name: "",
email: "",
age: ""
}}
onSubmit={onSubmitHandler}
render={props => {
return (
<Form>
<label>
Name
<Field
name="name"
type="text"
placeholder="name"
onChange={e => {props.handleChange(e); onChangeHandler(e)}}
/>
</label>
<button type="submit">Submit</button>
</Form>
);
}}
/>
</div>
);
}
Is there a way to do a similar thing as in onSubmitHandler, where Formik automagically outputs the value of the input without having to call these 2 functions in the onChange?
Thanks
Every field component receives a field prop which has a value prop containing the value for that field, as well as a form prop containing helper methods that you can use to set the value. I'd need to see the structure of your code to give specific suggestions on how to implement what you want, but you can emulate the default functionality by calling form.setFieldValue(field.name, field.value). In addition, the field prop has this handler built in by default in the field.onChange prop.

Resources