Formik Reset callback creates recursive issue - javascript

I have a very simple formik setup where I need to pass the new initial values when users press reset form button. I am following doc but I end up creating recursive issue.
formReset() is passed to formik as a param of onReset. The function is called but I am not sure where is the recursion happening.
Here is a codesandbox for your convenient. Change form value then try to reset the form.
App.js
// Helper styles for demo
import "./helper.css";
import { MoreResources, DisplayFormikState } from "./helper";
import React from "react";
import { render } from "react-dom";
import { Formik } from "formik";
import * as Yup from "yup";
const formReset = (_, {resetForm}) => {
resetForm({email: ''});
}
const App = () => (
<div className="app">
<h1>
Basic{" "}
<a
href="https://github.com/jaredpalmer/formik"
target="_blank"
rel="noopener noreferrer"
>
Formik
</a>{" "}
Demo
</h1>
<Formik
initialValues={{ email: "populate#test.com" }}
onSubmit={async values => {
await new Promise(resolve => setTimeout(resolve, 500));
alert(JSON.stringify(values, null, 2));
}}
onReset={formReset}
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required("Required")
})}
>
{props => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email" style={{ display: "block" }}>
Email
</label>
<input
id="email"
placeholder="Enter your email"
type="text"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.email && touched.email
? "text-input error"
: "text-input"
}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
<button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</button>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
<DisplayFormikState {...props} />
</form>
);
}}
</Formik>
<MoreResources />
</div>
);
render(<App />, document.getElementById("root"));

Edit:
So... a better option would be to use initialValues in useState and pass enableReinitialize and change the state to "reset" the form. It's more easy than trying to use resetForm.
You don't need to pass a function to onReset and call resetForm, you can do that by just pass the type reset to the button and have the Form component instead of normal html form tag.
The Form component will handle the handleReset that will be trigger when you have a button with type="reset".
<Form>
{/* other components */}
<button
type="reset"
className="outline"
disabled={!dirty || isSubmitting}
>
Reset
</button>
</Form>
Here is a working example.

Related

How to prevent a form input value from disappearing on click in other fields?

The form campaignid input value is automatically filled in when the page is loaded. When I click on the other fields, it reset to default.
What can I do to prevent default and preserve my campaignid input value?
I'm using a third party script to get URL parameters and insert them into campaignid value.
import { Button, FormControl, Input } from "#chakra-ui/react";
import { Field, Form, Formik } from "formik";
import Script from "next/script";
export default function FormikExample() {
return (
<>
<Script src="https://cdn.jsdelivr.net/gh/gkogan/sup-save-url-parameters/sup.min.js" />
<Formik
initialValues={{
name: "",
}}
onSubmit={(values) => {
setTimeout(() => {
fetch(`https://hooks.zapier.com/hooks/catch/3660927/bte5w77/`, {
method: "POST",
body: JSON.stringify(values, null, 2),
}),
3000;
});
}}
>
{(props) => (
<Form id="hero">
<Field name="name">
<FormControl>
{({ field }) => <Input {...field} type="text" id="name" />}
</FormControl>
</Field>
<Field name="email">
<FormControl>
{({ field }) => <Input {...field} type="email" id="email" />}
</FormControl>
</Field>
<Field name="campaignid">
{({ field }) => (
<FormControl isReadOnly>
<Input {...field} type="hidden" value="" id="campaignid" />
</FormControl>
)}
</Field>
<Button id="submited" isLoading={props.isSubmitting} type="submit">
Submit
</Button>
</Form>
)}
</Formik>
</>
);
}
You need to prevent the default of the submit form.
Use prevent default on your submit function, it will stop that behaviour. More info: https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault
e.preventDefault()

react-hook-form doesn't work when using material UI button

This is my code without material UI button:
const {register, handleSubmit} = useForm();
const onSubmit = (data) => {
console.log(data)
}
const handleChange = (e) => {
console.log(e.target.files)
}
...
<form id="myFile" onSubmit={handleSubmit(onSubmit)}>
<input id="file1" type="file" {...register("file1")} onChange={handleChange}/>
<input type="submit"/>
</form>
This works for me, but when I try to add material UI button instead of input, I get onChange value but when I click submit. I don't get any form data.
const {register, handleSubmit} = useForm();
const onSubmit = (data) => {
console.log(data)
}
const handleChange = (e) => {
console.log(e.target.files)
}
...
<form id="myFile" onSubmit={handleSubmit(onSubmit)}>
<input id="file1" type="file" {...register("file1")} onChange={handleChange}
style={{display:"none"}}/>
<label htmlFor="file1">
<Button variant="contained" component="span">
Choose file
</Button>
</label>
<input type="submit"/>
</form>
Is there any solution here?
You are forget to mention the type of the button
for Default material ui button type is
type="button"
Check this git Document
you should mention
type="submit"
So do like this
<Button type="submit" variant="contained" component="span">
Choose file
</Button>
You can try something like this:
import React, { useState } from 'react';
import { Button, TextField } from '#material-ui/core';
import useForm from 'react-hook-form';
import { object, string } from 'yup';
const Form: React.FC = () => {
const schema = object().shape({
username: string().required('username required'),
password: string().required('password required'),
});
const { register, handleSubmit, errors } = useForm({ validationSchema: schema });
const onSubmit = (data: any) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
name="username"
error={!!errors.username}
label="Username"
helperText={errors.username ? errors.username.message : ''}
type="email"
inputRef={register}
fullWidth
/>
<TextField
name="password"
error={!!errors.password}
label="Password"
inputRef={register}
helperText={errors.password ? errors.password.message : ''}
type="password"
fullWidth
/>
<Button
color="secondary"
type="submit"
variant="contained"
fullWidth
>
Submit
</Button>
</form>
);
};
ref: How to use react-hook-form with ant design or material UI
Currently your submit event is controlled by the whole form element <form onSubmit={handleSubmit(onSubmit)}> but this is possible only when the form has a relevant submit button like <input type="submit"/> but when you use materail ui, MUI button doesnt distincts submit type even if mentioned.
So the solution is to move your onSubmit function at form to down the onClick event of your new MUI button.
The code that works is:
<Button onClick={handleSubmit(onSubmit)} type="submit" variant="contained" component="span"> Submit </Button>

How to clear react-select value after formik form submission?

I have a formik form where I have used react-select for select list. Here is my form:
import React from "react";
import { ErrorMessage, Field, Form, Formik } from "formik";
import * as Yup from "yup";
import { Button, Col, FormGroup } from "reactstrap";
import Select from "react-select";
const AddBankForm = (props) => {
return (
<Formik
initialValues={{
district: props.districts,
}}
validationSchema={Yup.object({
district: Yup.string().required("Required"),
})}
onSubmit={(values, actions) => {
setError(null);
setMessage(null);
try {
const response = await postDataWithAuth(DISTRIBUTOR_BANK_ADD, {
routing_number: values.branch,
bank_account_number: values.accountNumber,
account_holder_name: values.accountName,
pin_number: values.tpin,
});
// This is not working
actions.resetForm();
setMessage(response.message);
} catch (e) {
setError(e.response.data);
}
actions.setSubmitting(false);
}}
>
{(formikProps) => (
<Form onSubmit={formikProps.handleSubmit} autoComplete="one-time-code">
<div className="form-row">
<Col>
<FormGroup>
<label>
District<span className="text-danger">*</span>
</label>
<Select
menuPortalTarget={document.body}
type="text"
name="district"
onChange={(option) => {
props.updateDistrict(option.value);
formikProps.setFieldValue("district", option.value);
}}
options={
props.isCreateLiftingSuccessful ? [] : props.districts
}
onBlur={formikProps.handleBlur}
/>
<ErrorMessage
name="district"
component="div"
className="text-danger"
/>
</FormGroup>
</Col>
</div>
<div className="form-row mt-3 text-center">
<Col>
<Button
className="btn btn-success"
type="submit"
disabled={!formikProps.dirty || formikProps.isSubmitting}
>
Submit
</Button>
</Col>
</div>
</Form>
)}
</Formik>
);
};
The problem is that the react-select field is not getting cleared after the form submission. I have used formik's resetForm() method to clear my form. But it seems that resetForm method does not have any impact on the react-select field.
You can use 'ref' props for clear react-select field.
import React from "react";
import { ErrorMessage, Field, Form, Formik } from "formik";
import * as Yup from "yup";
import { Button, Col, FormGroup } from "reactstrap";
import Select from "react-select";
const AddBankForm = (props) => {
// update start
let selectRef = null;
const clearValue = () => {
selectRef.select.clearValue();
};
// update end
return (
<Formik
initialValues={{
district: props.districts,
}}
validationSchema={Yup.object({
district: Yup.string().required("Required"),
})}
onSubmit={(values, actions) => {
setError(null);
setMessage(null);
try {
const response = await postDataWithAuth(DISTRIBUTOR_BANK_ADD, {
routing_number: values.branch,
bank_account_number: values.accountNumber,
account_holder_name: values.accountName,
pin_number: values.tpin,
});
// This is not working
actions.resetForm();
// Try this way
clearValue();
setMessage(response.message);
} catch (e) {
setError(e.response.data);
}
actions.setSubmitting(false);
}}
>
{(formikProps) => (
<Form onSubmit={formikProps.handleSubmit} autoComplete="one-time-code">
<div className="form-row">
<Col>
<FormGroup>
<label>
District<span className="text-danger">*</span>
</label>
<Select
// use ref
ref={ref => {
selectRef = ref;
}}
menuPortalTarget={document.body}
type="text"
name="district"
onChange={(option) => {
props.updateDistrict(option.value);
formikProps.setFieldValue("district", option.value);
}}
options={
props.isCreateLiftingSuccessful ? [] : props.districts
}
onBlur={formikProps.handleBlur}
/>
<ErrorMessage
name="district"
component="div"
className="text-danger"
/>
</FormGroup>
</Col>
</div>
<div className="form-row mt-3 text-center">
<Col>
<Button
className="btn btn-success"
type="submit"
disabled={!formikProps.dirty || formikProps.isSubmitting}
>
Submit
</Button>
</Col>
</div>
</Form>
)}
</Formik>
);
};

Using formik.isValid on Submit Button when Button is outside of Formik Component

My React app has a react-bootstrap Bootstrap Modal that contains a Formik form in Modal.Body and the submit button in Modal.Footer.
How can we allow the disabled attribute of the button inside Modal.Footer to accept the formik.isValid and formik.dirty values?
disabled={!(formik.isValid && formik.dirty)}
More Complete Code
import React, { useState, useEffect } from 'react';
import { Button, Modal, Form } from 'react-bootstrap';
import { Formik } from 'formik';
export function NicknameModal({show, handleClose}) {
return (
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>My Title</Modal.Title>
</Modal.Header>
<Modal.Body>
<Formik
initialValues={{
nickname: '',
}}
onSubmit={(
values,
{ setSubmitting }
) => {
setSubmitting(true);x
handleClose();
setSubmitting(false);
}}
>
{({values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, setFieldValue }) => (
<Form id="nicknameForm" onSubmit={handleSubmit}>
<Form.Group controlId="formNickname">
<Form.Label>Nickname</Form.Label>
<Form.Control type="text" name="nickname" onChange={handleChange} onBlur={handleBlur} value={values.nickname} />
</Form.Group>
</Form>
)}
</Formik>
</Modal.Body>
<Modal.Footer>
<Button variant="primary" type="submit" form="nicknameForm">Apply</Button>
</Modal.Footer>
</Modal>
)
}
you can use useRef property of React to take Formik control outside of the Formik component. Please documentation for innerRef of Formik
import React, { useState, useEffect, useRef } from 'react';
import { Button, Modal, Form } from 'react-bootstrap';
import { Formik } from 'formik';
export function NicknameModal({show, handleClose}) {
const formRef = useRef();
return (
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>My Title</Modal.Title>
</Modal.Header>
<Modal.Body>
<Formik
initialValues={{
nickname: '',
}}
innerRef={formRef}
onSubmit={(
values,
{ setSubmitting }
) => {
setSubmitting(true);x
handleClose();
setSubmitting(false);
}}
>
{({values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, setFieldValue }) => (
<Form id="nicknameForm" onSubmit={handleSubmit}>
<Form.Group controlId="formNickname">
<Form.Label>Nickname</Form.Label>
<Form.Control type="text" name="nickname" onChange={handleChange} onBlur={handleBlur} value={values.nickname} />
</Form.Group>
</Form>
)}
</Formik>
</Modal.Body>
<Modal.Footer>
<Button variant="primary" type="submit"
disabled={!(formRef.current.isValid && formRef.current.dirty)}
form="nicknameForm">Apply</Button>
</Modal.Footer>
</Modal>
)
}
the button would never work because it is outside the form, I refactored it just added the yup dependency, here is the test : https://codesandbox.io/s/ancient-dew-6e9thm

React Redux Form: form is submiting with old values

I have a FieldArray in Redux Form that I push objects inside this array and right after that I have a callback to trigger a function to make a POST request.
When I submit the form I get the old values, because the push() method of the Redux Form is an async dispach from redux.
// Parent component
<FieldArray
name="myObjects"
component={ChildComponent}
onSubmitObjects={this.onSubmit} />
// Function
onSubmit = async () => {
const { getFormValues } = this.props;
const data = {
myObjects: getFormValues.myObjects
}
try {
// const contact = await Service.updateUser(data);
} catch (e) {
console.log(e)
}
}
I need to submit the form with the new values added in the array right after the push method.
// Function inside ChildComponent
addNewObject = () => {
const { fields, onSubmitObjects} = this.props;
fields.push({
number: 1,
name: 'Foo',
});
if (onSubmitObjects) {
onSubmitObjects(); // cb() to trigger a function in the parent component
}
}
Is there a way to call the callback with the new values right after the push method?
You should use a form with redux-form handleSubmit to wrap your FieldArray. You can optionally pass your custom submit function (to make API requests, submit validation, etc.) to handleSubmit so it'd look like this <form onSubmit={handleSubmit(this.onSubmit)}> ...
See this example from redux-form official docs:
FieldArraysForm.js
import React from 'react'
import {Field, FieldArray, reduxForm} from 'redux-form'
import validate from './validate'
const renderField = ({input, label, type, meta: {touched, error}}) => (
<div>
<label>{label}</label>
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
)
const renderHobbies = ({fields, meta: {error}}) => (
<ul>
<li>
<button type="button" onClick={() => fields.push()}>Add Hobby</button>
</li>
{fields.map((hobby, index) => (
<li key={index}>
<button
type="button"
title="Remove Hobby"
onClick={() => fields.remove(index)}
/>
<Field
name={hobby}
type="text"
component={renderField}
label={`Hobby #${index + 1}`}
/>
</li>
))}
{error && <li className="error">{error}</li>}
</ul>
)
const renderMembers = ({fields, meta: {error, submitFailed}}) => (
<ul>
<li>
<button type="button" onClick={() => fields.push({})}>Add Member</button>
{submitFailed && error && <span>{error}</span>}
</li>
{fields.map((member, index) => (
<li key={index}>
<button
type="button"
title="Remove Member"
onClick={() => fields.remove(index)}
/>
<h4>Member #{index + 1}</h4>
<Field
name={`${member}.firstName`}
type="text"
component={renderField}
label="First Name"
/>
<Field
name={`${member}.lastName`}
type="text"
component={renderField}
label="Last Name"
/>
<FieldArray name={`${member}.hobbies`} component={renderHobbies} />
</li>
))}
</ul>
)
const FieldArraysForm = props => {
const {handleSubmit, pristine, reset, submitting} = props
return (
<form onSubmit={handleSubmit}>
<Field
name="clubName"
type="text"
component={renderField}
label="Club Name"
/>
<FieldArray name="members" component={renderMembers} />
<div>
<button type="submit" disabled={submitting}>Submit</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
)
}
export default reduxForm({
form: 'fieldArrays', // a unique identifier for this form
})(FieldArraysForm)

Categories