Formik: React Formik updating one field based on another

Created on 24 Sep 2019  路  9Comments  路  Source: formium/formik

鉂換uestion

I'm using formik react library and trying to update 2 fields, based on the onChange event of anothers. 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

stale

Most helpful comment

@rahi-nz Could you kindly format your code? It is very hard to read.

I stumbled upon this issue when researching whether there is a more elegant solution than ours. This is what we did (pseudo-code):

onChange={(e) => {
    formikProps.handleChange(e);
    formikProps.values.dependentValue = calcDependent(e.target.value);
}}

I find this rather disturbing, but we could not think of a better way to do it. Can you, @jaredpalmer?

I find the above disturbing because

  • it throws the abstraction that Formik provides out of the window (e.target.value)
  • it modifies the state of something that does not have a specified API. Not only is this hacky, but it might also stop working with any release.

All 9 comments

@rahi-nz Could you kindly format your code? It is very hard to read.

I stumbled upon this issue when researching whether there is a more elegant solution than ours. This is what we did (pseudo-code):

onChange={(e) => {
    formikProps.handleChange(e);
    formikProps.values.dependentValue = calcDependent(e.target.value);
}}

I find this rather disturbing, but we could not think of a better way to do it. Can you, @jaredpalmer?

I find the above disturbing because

  • it throws the abstraction that Formik provides out of the window (e.target.value)
  • it modifies the state of something that does not have a specified API. Not only is this hacky, but it might also stop working with any release.

@rahi-nz Could you kindly format your code? It is very hard to read.

I stumbled upon this issue when researching whether there is a more elegant solution than ours. This is what we did (pseudo-code):

onChange={(e) => {
    formikProps.handleChange(e);
    formikProps.values.dependentValue = calcDependent(e.target.value);
}}

I find this rather disturbing, but we could not think of a better way to do it. Can you, @jaredpalmer?

I find the above disturbing because

  • it throws the abstraction that Formik provides out of the window (e.target.value)
  • it modifies the state of something that does not have a specified API. Not only is this hacky, but it might also stop working with any release.

Thanks @alexbepple
It worked like a charm
I use it like this :

Edit 5tjrr

you should use setFieldValue to update values, may something like

onChange={(e) => {
formikProps.handleChange(e);
formikProps.setFieldValue('dependentValue',calcDependent(e.target.value));
}}

you should use setFieldValue to update values, may something like

onChange={(e) => {
formikProps.handleChange(e);
formikProps.setFieldValue('dependentValue',calcDependent(e.target.value));
}}

setFieldValue is an event callback that only updates the ref after each render so it doesn't work because every time return previous value in others field.

alexbepple's way works well:

onChange={(e) => {
formikProps.handleChange(e);
formikProps.values.dependentValue = calcDependent(e.target.value);
}}

You can also use an effect and a child component. Theres been some discussion of change listeners but we haven't found the ideal API for it yet.

In child component: (apologies for mobile formatting)

const formik = useFormikContext();

React.useEffect(() =>
    formik.setFieldValue("dependentField", calculateDependentValue(formik.values.mainField);
}, [formik.values.mainField]);

Pros are a more decentralized approach, cons are extra components and a render where they aren't in sync.

I'd recommend against changing properties directly on formik.values! Treat it as an immutable object.

We ended up doing something slightly more kosher than what I outlined above. onChange we calculate the entire new data structure representing the form. Pseudo code: onChange={e => formik.setValues(updateAllDependentFormValues(e.target.value, formik.values))}.

It's a bit strange that setFieldValue behaves differently from setValues (at least in version 1.4.1).

However, we are generally fairly happy with this approach because

  • it uses the API (setValues) instead of hackily modifying formik.values directly
  • this approach led us to separating the form logic (data structure + operations on this structure) from the presentation

/cc @alexkolson

We ended up doing something slightly more kosher than what I outlined above. onChange we calculate the entire new data structure representing the form. Pseudo code: onChange={e => formik.setValues(updateAllDependentFormValues(e.target.value, formik.values))}.

It's a bit strange that setFieldValue behaves differently from setValues (at least in version 1.4.1).

However, we are generally fairly happy with this approach because

  • it uses the API (setValues) instead of hackily modifying formik.values directly
  • this approach led us to separating the form logic (data structure + operations on this structure) from the presentation

/cc @alexkolson

but can i do this with an array / objects nested values?

@ahmad-reza619 yes, though I'm not sure how I can explain further without an example. All you're doing is changing your entire values object, and given the name and value of an input and the previous values you should be able to do that. Example:

```tsx
// getIn and setIn are helpers exported from Formik which get and set values in complex objects,
// returns a new object if setIn causes a change
// example field names for brevity
const updateAllDependentValues = (name = "myObj.myField", value, prevValues) => {
let newValues = setIn(prevValues, name, value);
if (getIn(prevValues, name) !== getIn(newValues, name)) {
// value changed, set other values
newValues = setIn(newValues, "myObj.otherField", calculateOtherValue(value);
}
return newValues;
}

In my own case, I handled the situation by creating a function like below

const handleAgeChange = (handler) => (e) => {
    const { target } = e;    
    const { value } = target;
    const computedValue =  computeDependentValue(value);
    handler({ target });

    // computedInputName is the value of the name attribute of the input whose value is to be computed
    handler({ target: { name: 'computedInputName', value: computedValue } });
};

Then for the onChange attribute of the input I pass in as below

onChange={handleAgeChange(handleChange)} // passing in formik's own handleChange as the handler
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dearcodes picture dearcodes  路  3Comments

najisawas picture najisawas  路  3Comments

jaredpalmer picture jaredpalmer  路  3Comments

ancashoria picture ancashoria  路  3Comments

jaredpalmer picture jaredpalmer  路  3Comments