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!
Related
i try make my first App in react, everything works, but i try take amoun of bill and number of people and divide te bill for person, and add Tip. I know for somebody probably its 5 min solution but i start working with that yesterday and still can't find solution.
I wanna take bill amount and divide by personAmount + tip if somebody choose tip. What i do wrong
import React, { useState } from 'react'
import style from './BillSplitter.module.css'
const tips = [5, 10, 15, 25]
const Input = ({ label, id, handleChange, name, type, placeholder }) => (
<>
<label className={`${style.label}`} htmlFor={id}>{label}</label>
<input className={`${style.input}`} type={type || "number"} id={id} name={name || id} placeholder={placeholder} onChange={handleChange}></input>
<br />
</>
);
const SelectTip = ({ tip }) => {
<>
<button className='tipButton' value={tip}></button>
</>
}
function BillSplitter() {
const [tipAmount, setTipAmount] = useState(0)
const [billAmount, setBillAmount] = useState(0)
const [personAmount, setPersonAmount] = useState(0)
function handleChange(e) {
console.log(e.target.value)
}
return (
<div className={`${style.wrapper}`}>
<div className={`${style.box}`}>
<div className={`${style.top}`}>
<h2 className={`${style.title}`}>Bill Splitter</h2>
<p className={`${style.personBillAmount}`}>Person Bill Amount</p>
<p onChange={handleChange} className={`${style.totalPersonAmount}`} >$ {tipAmount}</p>
</div>
<div className={`${style.bottom}`}>
<Input handleChange={handleChange} className={`${style.clases}`} placeholder='Amount of bill' label="Bill value">{billAmount}</Input>
<Input handleChange={handleChange} className={`${style.clases}`} placeholder='Number of people' label="Number of people">{personAmount}</Input>
<div className={`${style.tipBox}`}>
<p>Select tip</p>
{tips.map((tip) => {
return <button className={`${style.tipButton}`}>{tip} %</button>
})}
</div>
</div>
</div>
</div>
)
}
export default BillSplitter
Added onChange handler to the Input component and pass the different input setter functions. Then do the calculation when the tip is selected.
Try like below:
const tips = [5, 10, 15, 25];
const Input = ({ label, id, handleChange, name, type, placeholder }) => (
<React.Fragment>
<label htmlFor={id}>{label}</label>
<input
type={type || "number"}
id={id}
name={name || id}
placeholder={placeholder}
onChange={(e) => handleChange(e.target.value)}
></input>
<br />
</React.Fragment>
);
function BillSplitter() {
const [billAmount, setBillAmount] = React.useState(0);
const [personAmount, setPersonAmount] = React.useState(0);
const [perCost, setPerCost] = React.useState(0);
const calculatePerPeronAmount = (tip) => {
if (personAmount > 0) {
setPerCost(((1 + 0.01 * tip) * billAmount) / personAmount);
}
};
return (
<div>
<div>
<div>
<h2>Bill Splitter</h2>
<p>Person Bill Amount</p>
<p>$ {perCost}</p>
</div>
<div>
<Input
handleChange={setBillAmount}
placeholder="Amount of bill"
label="Bill value"
>
{billAmount}
</Input>
<Input
handleChange={setPersonAmount}
placeholder="Number of people"
label="Number of people"
>
{personAmount}
</Input>
<div>
<p>Select tip</p>
{tips.map((tip) => {
return (
<button onClick={() => calculatePerPeronAmount(tip)} key={tip}>
{tip} %
</button>
);
})}
</div>
</div>
</div>
</div>
);
}
ReactDOM.render(<BillSplitter />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
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>
);
};
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.
I am created a dynamic form in react.js but i can not type anything value in input because onchnage function not working i don't know why i tried a lot of times but i am getting failed and adding form and deleting form is working all right only input value not working here is my code and codesandbox link https://codesandbox.io/s/reactdynamicform-02cho .
import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputFields: [
{
firstName: "",
lastName: ""
}
]
};
}
handleAddFields = () => {
const values = this.state.inputFields;
values.push({ firstName: "", lastName: "" });
this.setState({
values
});
};
handleRemoveFields = index => {
const values = this.state.inputFields;
values.splice(index, 1);
this.setState({
values
});
};
async onChange(e, index) {
if (
["firstName","lastName"].includes(e.target.name)
) {
let cats = [...this.state.inputFields];
cats[index][e.target.name] = e.target.value;
await this.setState({
cats
});
}else{
this.setState({ [e.target.name]: e.target.value.toUpperCase() });
}
console.log(this.state.inputFields);
}
handleSubmit = e => {
e.preventDefault();
console.log("inputFields", this.state.inputFields);
};
render() {
return (
<>
<h1>Dynamic Form Fields in React</h1>
<form onSubmit={this.handleSubmit.bind(this)}>
<div className="form-row">
{this.state.inputFields.map((inputField, index) => (
<div key={`${inputField}~${index}`}>
<div className="form-group col-sm-6">
<label htmlFor="firstName">First Name</label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={inputField.firstName}
onChange={this.onChange.bind(index)}
/>
</div>
<div className="form-group col-sm-4">
<label htmlFor="lastName">Last Name</label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={inputField.lastName}
onChange={this.onChange.bind(index)}
/>
</div>
<div className="form-group col-sm-2">
<button
className="btn btn-link"
type="button"
onClick={() => this.handleRemoveFields(index)}
>
-
</button>
<button
className="btn btn-link"
type="button"
onClick={() => this.handleAddFields()}
>
+
</button>
</div>
</div>
))}
</div>
<div className="submit-button">
<button
className="btn btn-primary mr-2"
type="submit"
// onSubmit={this.handleSubmit}
>
Save
</button>
</div>
<br />
<pre>{JSON.stringify(this.state.inputFields, null, 2)}</pre>
</form>
</>
);
}
}
export default App;
You approach is not the correct. Use object to contain form values
state = {
inputFields: { firstName: '', lastName: '' }
}
onChange = (e) => {
const { name, value } = e.target;
this.setState(prevState => ({ inputFields: { ...prevState.inputFields, [name]: value } }));
}
// in jsx
<input name="firstName" onChange={this.onChange} />
try this
onChange={(e)=>{this.onChange(e, index)}}
instead of
onChange={this.onChange.bind(index)}
1) Since your inputFields state is an array, you can't just call this.state.inputFields.firstName and even less inputField.firstName.
You have to call this.state.inputsFields[0].firstName.
2) If you want the index AND the event, you have to pass the onChange event like this :
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
onChange={event => this.handleChange(event, index)}
/>
handleChange = (event, index) => {
console.log(event.currentTarget.value, index);
};
// output : {whatever you type} {index of the form}
// exemple : "hello 1"
How to set defaultValue to input component?
<Field name={`${prize}.rank`} defaultValue={index} component={Input} type='text'/>
I tried like above but my fields are empty. I'm trying to create fieldArray (dynamic forms):
{fields.map((prize, index) =>
<div key={index} className="fieldArray-container relative border-bottom" style={{paddingTop: 35}}>
<small className="fieldArray-title marginBottom20">Prize {index + 1}
<button
type="button"
title="Remove prize"
className="btn btn-link absolute-link right"
onClick={() => fields.remove(index)}>Delete</button>
</small>
<div className="row">
<div className="col-xs-12 col-sm-6">
<Field name={`${prize}.rank`} defaultValue={index} component={Input} type='text'/>
<Field name={`${prize}.prizeId`} defaultValue={index} component={Input} type='text'/>
<Field
name={`${prize}.name`}
type="text"
component={Input}
label='Prize Name'/>
</div>
<div className="col-xs-12 col-sm-6">
<Field
name={`${prize}.url`}
type="text"
component={Input}
label="Prize URL"/>
</div>
<div className="col-xs-12">
<Field
name={`${prize}.description`}
type="text"
component={Input}
label="Prize Description" />
</div>
</div>
</div>
)}
On redux forms you can call initialize() with an object of values like so:
class MyForm extends Component {
componentWillMount () {
this.props.initialize({ name: 'your name' });
}
//if your data can be updated
componentWillReceiveProps (nextProps) {
if (/* nextProps changed in a way to reset default values */) {
this.props.destroy();
this.props.initialize({…});
}
}
render () {
return (
<form>
<Field name="name" component="…" />
</form>
);
}
}
export default reduxForm({})(MyForm);
This way you can update the default values over and over again, but if you just need to do it at the first time you can:
export default reduxForm({values: {…}})(MyForm);
This jsfiddle has an example
https://jsfiddle.net/bmv437/75rh036o/
const renderMembers = ({ fields }) => (
<div>
<h2>
Members
</h2>
<button onClick={() => fields.push({})}>
add
</button>
<br />
{fields.map((field, idx) => (
<div className="member" key={idx}>
First Name
<Field name={`${field}.firstName`} component="input" type="text" />
<br />
Last Name
<Field name={`${field}.lastName`} component="input" type="text" />
<br />
<button onClick={() => fields.remove(idx)}>
remove
</button>
<br />
</div>
))}
</div>
);
const Form = () => (
<FieldArray name="members" component={renderMembers} />
);
const MyForm = reduxForm({
form: "foo",
initialValues: {
members: [{
firstName: "myFirstName"
}]
}
})(Form);
this is my implementation using a HoC
import { Component } from 'react'
import {
change,
} from 'redux-form'
class ReduxFormInputContainer extends Component{
componentDidMount(){
const {
initialValue,
meta,
} = this.props
if(initialValue === undefined || meta.initial !== undefined || meta.dirty) return
const {
meta: { form, dispatch },
input: { name },
} = this.props
dispatch(change(form, name, initialValue))
}
render(){
const {
initialValue,
component: Compo,
...fieldProps
} = this.props
return <Compo {...fieldProps} />
}
}
function reduxFormInputContainer(component){
return function(props){
return <ReduxFormInputContainer {...props} component={component} />
}
}
export default reduxFormInputContainer
and then for exemple:
import reduxFormInputContainer from 'app/lib/reduxFormInputContainer'
InputNumericWidget = reduxFormInputContainer(InputNumericWidget)
class InputNumeric extends Component{
render(){
const props = this.props
return (
<Field component={InputNumericWidget} {...props} />
)
}
}