jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
34.02k stars 2.79k forks source link

React Formik updating one field based on another #1840

Open rahi-nz opened 5 years ago

rahi-nz commented 5 years ago

❓Question

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

alexbepple commented 5 years ago

@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

rahi-nz commented 5 years ago

@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

krudos commented 4 years ago

you should use setFieldValue to update values, may something like

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

rahi-nz commented 4 years ago

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

johnrom commented 4 years ago

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.

alexbepple commented 4 years ago

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

/cc @alexkolson

ahmad-reza619 commented 4 years ago

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?

johnrom commented 4 years ago

@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:


// 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;
}
Qausim commented 4 years ago

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
JasonGarcia131 commented 2 years ago

I came across the same problem. I’ve been able to update the field values state by mapping through the independent options and setting a state variable based on the option selected. Ex:

setIndependentStateVariable((prevData)=>({…prevData, key: yourInitialValuesName.value})

// map through the state variable and filter the values you need const dependentOptions = independentStateVaribale.filter(item=>({relevant code}))

// map through the filtered array. Check if filtered array is not undefined. I used conditional rendering with a ternary.

// also make sure to set the name in the select options to the name of the initial values with the current index.

I hope this helps someone!

devdariill commented 1 year ago

I did the update in the submit, assigning the value like this

<Formik
      onSubmit={async (values, actions) => {
            values.numite=values.candet;
            values.totdet=values.candet*values.valuni;
            }
>