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

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

Related

Modal not popping up at react and sends to an empty page after click of a button

Hello on ReactJS Bootstrap. When I try to click my button for the handleShow of the Modal
It sends me to a blank webpage and just get's stuck there. I tried to debug and my state show changes to true but it never shows the modal.
added text since stack overflow does not want me to post if the wording is not a lot, added text since stack overflow does not want me to post, added text since stack overflow does not want me to post
Any help would be appreciated.
Thanks!
import React, { Component, useState } from "react";
import Modal from "react-bootstrap/Modal";
import { useHistory } from "react-router-dom";
import Axios from "axios";
// import Profile from '../pages/forms';
import { Link } from "react-router-dom";
import Form from "react-bootstrap/Form";
import "../index.css";
import { setGlobalState, useGlobalState } from "../globals/globalVar";
function LoginComponent(props) {
// const
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [show, setShow] = useState(false);
const history = useHistory();
// make an axios query
const url = useGlobalState("defaultUrl");
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const handleEmailAndpasswordVerify = () => {
if (email.length === 0) {
alert("Email cannot be empty.");
return -1;
}
// email must contain #csub.edu
if (!email.includes("#csub.edu")) {
alert("Email must contain #csub.edu");
return -1;
}
// if the len of password is less than 8 then reject
if (password.length < 8) {
alert("Password must be at least 8 characters long.");
return -1;
}
return 0;
};
const handleSubmit = (e) => {
if (handleEmailAndpasswordVerify() !== 0) {
// if the email and password are valid then send the request to the server
// to register
// send the request to the server
// if success then close the modal
// if fail then alert the error
return;
}
e.preventDefault();
// make a post axios request to a server
Axios.post("http://localhost:8000/index.php", {
email: email,
password: password,
}).then((response) => {
if (response.data.message) {
console.log(response.data);
// console.log(loginStatus);
} else {
// alert the error
alert(response.data.error);
// history.push("/main");
}
});
setShow(false);
};
const login = () => {
Axios.post(url, {
email: email,
password: password,
}).then((response) => {
if (response.data.message) {
console.log(response.data);
// console.log(loginStatus);
} else {
// setLoginStatus(response.data[0].username);
history.push("/main");
}
});
};
//login validator
const loginValidate = () => {
if (email.length === 0) {
alert("Username cannot be empty.");
return;
}
if (password.length === 0) {
alert("Password cannot be empty.");
return;
}
login();
};
return (
<div>
<form>
<nav className="navbar navbar-expand-lg navbar-light fixed-top">
<div className="container">
<Link className="navbar-brand fs-1"> StuHuB </Link>{" "}
<div
className="collapse navbar-collapse"
id="navbarTogglerDemo02"
></div>{" "}
</div>{" "}
</nav>{" "}
<div className="auth-wrapper">
<div className="auth-inner">
<h3> Sign In </h3>{" "}
<div className="form-group">
<label> Login ID </label>{" "}
<input
type="text"
className="form-control"
placeholder="Enter Login ID"
onChange={(e) => {
setEmail(e.target.value);
}}
/>{" "}
</div>{" "}
<div className="form-group">
<label> Password </label>{" "}
<input
type="password"
className="form-control"
placeholder="Enter password"
onChange={(e) => {
setPassword(e.target.value);
}}
/>{" "}
</div>{" "}
<button
onClick={loginValidate}
type="button"
className="btn btn-primary btn-block"
>
Submit{" "}
</button>{" "}
</div>{" "}
<button className="btn btn-primary btn-block" onClick={handleShow}>
Register
</button>
</div>{" "}
</form>
{/* Not working? */}
{/* Modal */}
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group className="mb-3" controlId="exampleForm.ControlInput1">
<Form.Label>Email address</Form.Label>
<Form.Control
type="email"
placeholder="name#example.com"
autoFocus
onChange={(e) => setEmail(e.target.value)}
/>
</Form.Group>
{/* form password */}
<Form.Item
name="password"
label="Password"
rules={[
{
required: true,
message: "Please enter password!",
},
]}
>
<input
type="password"
onChange={(e) => setPassword(e.target.value)}
/>
</Form.Item>
</Form>
</Modal.Body>
<Modal.Footer>
<button variant="secondary" onClick={handleClose}>
Close
</button>
<button variant="primary" onClick={handleSubmit}>
Submit
</button>
</Modal.Footer>
</Modal>
{/* make a hidden modal */}
</div>
);
}
export default LoginComponent;

How to make modal appear onClick change?

I am stuck on getting my Modal to appear when the Edit button is clicked. I can see that the modalShow is being set to true when I click the button since I console.log the value. Any idea on how to get modal to appear when user clicks the edit button?
product-screen.jsx
import { useState } from 'react';
import { Button, Card, Form, Modal } from 'react-bootstrap';
import 'reactjs-popup/dist/index.css';
import ModalScreen from './modal-screen';
const ProductCardScreen = ({ product }) => {
const { productName, productPrice, productInStock, productDescription } =
product;
const [modalShow, setModalShow] = useState(false);
const [updateProduct, setProductUpdate] = useState({
productDescription,
productInStock,
productName,
productPrice,
});
const handleChange = (event) => {
console.log('event', event.target.value);
setProductUpdate({
...updateProduct,
[event.target.name]: event.target.value,
});
};
// todo
const saveUpdatedProduct = (product) => {
// save logic here to db
};
const handleDelete = () => {
alert('Are you sure you want to delete?');
};
const handleModalChange = () => {
console.log('called');
setModalShow(true);
};
return (
<div>
<Card>
<Card.Body>
<Card.Title>Product Name: {product.productName}</Card.Title>
<Card.Text>
Product Description: {product.productDescription}
</Card.Text>
<Card.Text>Product Quantity: {product.productInStock}</Card.Text>
<Card.Text>Price: ${product.productPrice}</Card.Text>
<div
style={{
float: 'right',
}}
>
<Button
style={{ margin: '10px' }}
onClick={() => handleModalChange()}
className='btn btn-primary'
variant='primary'
>
Edit
</Button>
<Button
onClick={() => handleDelete()}
className='btn btn-danger'
variant='primary'
>
Delete
</Button>
</div>
</Card.Body>
</Card>
<ModalScreen product={product} handleChange={handleChange} />
</div>
);
};
export default ProductCardScreen;
modal-screen.jsx
import { Button, Form, Modal } from 'react-bootstrap';
const ModalScreen = ({ product, handleChange }) => {
const { productName, productPrice, productInStock, productDescription } =
product;
return (
<Modal
animation={false}
size='lg'
aria-labelledby='contained-modal-title-vcenter'
centered
>
<Modal.Header>
<Modal.Title id='contained-modal-title-vcenter'>
Product Information
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group className='mb-3'>
<Form.Label>Name</Form.Label>
<Form.Control
onChange={handleChange}
value={productName}
type='text'
/>
</Form.Group>
<Form.Group className='mb-3'>
<Form.Label>Description</Form.Label>
<Form.Control
onChange={handleChange}
value={productDescription}
type='text'
/>
</Form.Group>
<Form.Group className='mb-3'>
<Form.Label>Amount In Stock</Form.Label>
<Form.Control
onChange={handleChange}
value={productInStock}
type='text'
/>
</Form.Group>
<Form.Group className='mb-3'>
<Form.Label>Price</Form.Label>
<Form.Control
onChange={handleChange}
value={`$${productPrice}`}
type='text'
/>
</Form.Group>
<Button variant='primary' type='button'>
Save
</Button>
<Button variant='danger' type='button'>
Cancel
</Button>
</Form>
</Modal.Body>
<Modal.Footer></Modal.Footer>
</Modal>
);
};
export default ModalScreen;

Dependency Formik form Unit testing

I want to write the unit test for React formik form fields.
Related packages versions are bellow
React version ^16.9.0
Formik version ^1.5.8
Jest version ^23.0.0
jest-enzyme version ^7.1.2
Enzyme version ^3.11.0
Component.js
const formInitialValues = {
frequency: null,
weekType: null'
};
<Formik
enableReinitialize
initialValues={formInitialValues}
onSubmit={(values, actions) => {
console.log(values);
}}
validate={values => {
// validation here
}}
render={props => (
<Form
onSubmit={props.handleSubmit}
data-test="smart-cabinet-add-doc-form"
>
<Row>
<div className="form-group col-md-6 ">
<label htmlFor="frequency" data-test="frequency-label">
Frequency
</label>
<CustomSelect
id="frequency"
name="frequency"
className="form-control col-sm-10"
options={SmartCabinetHelper.ADD_DOCUMENT_FREQUENCY_OPTIONS}
onChange={(name, value) => {
setFieldValue(name, value);
formatDueDate(values);
}}
onBlur={setFieldTouched}
placeholder="Select Frequency"
setFieldValue={setFieldValue}
value={values.frequency}
isDisabled={isNull(values.uploadedBy)}
data-test="frequency-field"
/>
<div className="error-message">
<ErrorMessage name="frequency" />
</div>
</div>
</Row>
{!isNull(values.frequency) && (
<Row>
<div className="form-group col-md-12 ">
<CustomSelect
id="weekType"
name="weekType"
className="form-control"
options={QuickLinkModalHelper.WEEKS_TYPES.slice(0, -1)}
onChange={(name, value) => {
setFieldValue(name, value);
formatDueDate(values);
}}
onBlur={setFieldTouched}
isSearchable={false}
placeholder=""
setFieldValue={setFieldValue}
value={values.weekType}
data-test="weekType-field"
/>
</div>
</Row>
)}
</form>
)}
/>
In the componentTest.js file I can test the frequency element, but I can't test the weekType element because that element depends on the frequency value.
{!isNull(values.frequency) && (
When the unit test runs, according to the formInitialValues, of formik form frequency value is Null. So I can't check weekType, It occurs an error. Test fails.
ComponentTest.js
const setup = () => {
return mount(
<Component />
);
};
describe('Test form elements', () => {
let wrapper;
beforeEach(() => {
wrapper = setup(defaultProps);
});
test('test frequency field', () => {
const frequencyField = findByTestAttr(
wrapper,
'frequency-field'
);
expect(frequencyField.length).toBe(1); // This test passes
});
test('test weekType field', () => {
const weekTypeField = findByTestAttr(
wrapper,
'weekType-field'
);
expect(weekTypeField.length).toBe(1); // This test fails, because frequency value = null
});
});
I tried too many ways to figure that. But couldn't. Is there any way to change formInitialValues in the testing file? Thank you.
I tried many ways to find a solution, finally found a way
Use mock formik form in test file except importing component.js using react-testing library
component.test.js
describe('Test form elements', () => {
it('test frequency field', async () => {
const { getByText, getByTestId } = render(
<Formik
initialValues={{
frequency: null,
weekType: null' }}
onSubmit={mock}
>
<Form
onSubmit={props.handleSubmit}
data-test="smart-cabinet-add-doc-form"
>
<Row>
<div className="form-group col-md-6 ">
<label htmlFor="frequency" data-test="frequency-label">
Frequency
</label>
<CustomSelect
id="frequency"
name="frequency"
...
/>
<div className="error-message">
<ErrorMessage name="frequency" />
</div>
</div>
</Row>
{!isNull(values.frequency) && (
<Row>
<div className="form-group col-md-12 ">
<CustomSelect
id="weekType"
name="weekType"
...
data-test="weekType-field"
/>
</div>
</Row>
)}
</form>
</Formik>
);
const weekTypeField = await waitForElement(() => getByTestId('weekType-field'));
expect(weekTypeField.length).toBe(1);
});
});
Any suggestions...
Thank you.

Formik Reset callback creates recursive issue

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.

Using setFieldValue for one field, based on another filed values

I'm using formik react library and trying to update 2 fields, based on the onChange event of another. For example,
price = quantity * totalPrice
price :
onChange={() => {setFieldValue('quantity',values.quantity? values.price / values.totalPrice:values.quantity, );
setFieldValue('totalPrice',values.totalPrice? values.price * values.quantity: values.totalPrice,);}}
quantity :
onChange={(value, e) => { this.disableFiled(value, e); setFieldValue('totalPrice',values.price ? values.price * values.totalPrice : ' ',);}}
totalPrice:
onChange={(value, e) => { this.disableFiled(value, e);setFieldValue('quantity',values.price ? values.totalPrice / price : ' ', ); }}
when quantity has value, total price will be disabled and vice versa.but it doesn't calculate other fields correctly
This is how I do this.
App.js file:
import React from "react";
import "./styles.css";
import { Formik } from "formik";
import * as Yup from "yup";
import CalculatedField from "./CalculatedField";
const App = () => (
<div className="app">
<Formik
initialValues={{ price: "", quantity: "", totalPrice: "" }}
onSubmit={async values => {
await new Promise(resolve => setTimeout(resolve, 500));
alert(JSON.stringify(values, null, 2));
}}
validationSchema={Yup.object().shape({
price: Yup.number("It's a number").required("Required"),
quantity: Yup.number("It's a number").required("Required"),
totalPrice: Yup.number("It's a number").required("Required")
})}
>
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
setFieldValue
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="input-row">
<label htmlFor="quantity" style={{ display: "block" }}>
Quantity
</label>
<input
id="quantity"
name="quantity"
placeholder="Enter quantity"
type="number"
value={values.quantity}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.quantity && touched.quantity
? "text-input error"
: "text-input"
}
/>
{errors.quantity && touched.quantity && (
<div className="input-feedback">{errors.quantity}</div>
)}
</div>
<div className="input-row">
<label htmlFor="price" style={{ display: "block" }}>
Price
</label>
<input
id="price"
name="price"
placeholder="Enter your price"
type="number"
value={values.price}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.price && touched.price
? "text-input error"
: "text-input"
}
/>
{errors.price && touched.price && (
<div className="input-feedback">{errors.price}</div>
)}
</div>
<div className="input-row">
<label htmlFor="totalPrice" style={{ display: "block" }}>
Total Price
</label>
<CalculatedField
id="totalPrice"
type="number"
name="totalPrice"
value={values.totalPrice}
values={values}
setFieldValue={setFieldValue}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.totalPrice && touched.totalPrice
? "text-input error"
: "text-input"
}
/>
{errors.totalPrice && touched.totalPrice && (
<div className="input-feedback">{errors.totalPrice}</div>
)}
</div>
<div className="input-row">
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</div>
</form>
);
}}
</Formik>
</div>
);
export default App;
CalculatedField.js
import React, { useEffect } from "react";
const CalculatedField = props => {
useEffect(() => {
var val = 0;
if (props.values.price && props.values.quantity) {
val = props.values.price * props.values.quantity;
}
props.setFieldValue("totalPrice", val);
}, [props.values]);
return (
<input
id="totalPrice"
type="number"
name="totalPrice"
value={props.values.totalPrice}
/>
);
};
export default CalculatedField;
This is basically achieved by calling setFieldValue method within useEffect hooks in the CalculatedField component. Please remember useEffect will watch for the change of the values and run the setFieldValue method when they are modified.
Please follow the CodeSandbox demo. https://codesandbox.io/s/affectionate-mirzakhani-who30?file=/src/App.js
Check this out it may help :
https://github.com/jaredpalmer/formik/issues/1840
you have to call handleChange(e) on-field change!

Categories