Formik: setFieldValue should return a Promise or receive a callback

Created on 21 Mar 2018  Â·  51Comments  Â·  Source: formium/formik

Feature

Current Behavior

setFieldValue() is async because it uses setState(), but it doesn't receive a callback or return anything.
That's why it's hard to get modified values right after that, for example if I want to run submitForm().

Desired Behavior

setFieldValue() will return a promise, so I could await it and get changed values.

Suggested Solutions

return a Promise from setFieldValue() OR add additional callback to function declaration

Medium Enhancement

Most helpful comment

I have added a PR, adding promise to the setFieldValue
#1398
@jaredpalmer can you take a look?
thanks

All 51 comments

Can you elaborate more on your use case?

Submitting form after every single change seems really strange to me.

I use Formik as a filter above a list, and changing some fields should emit instant submitting.
For example, setting checkboxes.
CodeSandbox stores a synthetic example, only for PoC.

Temporary (i hope) hacky workaround I am using - put submitForm into a setTimeout.

Yes, setTimeout made this job, and even Promise.resolve works, but both looks like kludges

A common use case for this is when you have a form with a set of filters, and you want your new filter values to be applied each time a filter is changed. With a callback option, this would look something like this:

<Select
  value={values.city}
  onChange={value => setFieldValue('city', value, submitForm)}
>
  {options.map(option =>
    <Option key={option.id} value={option.value}>{option.name}</Option>
  )}
</Select>

As @today- said, since setFieldValue uses setState (which is async), having some kind of callback would be nice for this type of use case.

I also need the callback function.
In some cases is very handy

It would be nice if FastField & Field exposed this callback as a prop as well.

any update to this as i also need callback or promise or at least a way of doing this sync

This is pretty essential. There are many cases after you update a value on a field, you want to have access to the value immediately, or perhaps the errors or touched state.

I just came across the same problem...I need to get a value after setFieldValuebut is not possible because setStateis _async_ (as it was already mentioned). Nice feature to have

I have this issue also. I have a dynamic dropdown that needs updating requiring me to make an api call. This forces me to submit the form before rerender so values are updated.

Edit: backend fix solved need for this workaround. Removed dependency & was able to get all data needed on load.

I also need a callback for pretty much the same usecase than @zakangelle

My use case is a form that has two separate submit buttons for "Save draft" and "Publish"; they set an is_draft field to true or false and then submit the form (in theory).

Exactly same use case with "Save draft". Missing a callback / promise :'(

I believe it's a very common use case for feature like this. In my project I want to provide a "shortcut" button that will pre-populate the form with preset values and submit the form right after. With the current implementation of setFieldValue this is impossible (without some workaround such as setTimeout).

I've got the same problem. I'd love setFieldValue to return a promise.

Any updates on this or a way to workaround? I have a similar issue to @ibolton336 where i have 2 select elements with the second element using the value of the first to filter the available options.

@rfabes21 here's my current workaround:

  <button
    type="button"
    onClick={async () => {
      setFieldValue('signed', true)
      // Hack to wait for new value to be applied
      // Pending https://github.com/jaredpalmer/formik/issues/529
      await Promise.resolve()
      submitForm()
    }}>
    Sign
  </button>

can this get marked as an enhancement so it doesnt get lost?

Seems like there is a enough requests here. Let me look into this.

To synthesize the above, the goal is to respond to a set value event inline (so as to use the updated state) somehow.

You could also consider enabling the same functionality for all set* methods.

yes, this would be very useful for setTouched, setError, etc. as well.

Als very interested indeed, my current workaround is using a timeout. Either a promise or an option for a callback would be fine for me.

<RadioGroup
    name="bla"
    value={values.bla}
    onChange={
        (
            e: SyntheticInputEvent<HTMLInputElement>
        ) => {
            setFieldValue(
                'bla',
                e.currentTarget.value
            )
            // Use setTimeout as setFieldvalue uses setState and is asynchronous so directly calling submitForm would result in submit without new value
            // TODO: refactor when eventually this issue is resolved in formik https://github.com/jaredpalmer/formik/issues/529
            setTimeout(() => submitForm(), 200)
        }
    }
/>

@ZwaarContrast I don't think you need to use such a big timeout value for setTimeout. I am using 1ms just fine:

    setFieldValue('blah', 'somevalue')
    setTimeout(submitForm, 1)

I also need to submit the form on a select change after setFieldValue.

Callback function would be also handy in case you have giant structure of data you want to use in formik and setting any value takes some time. It would be possible to set loading state and show loading bar before setFormValue and then use callback to let user now operation is done.

Hi everyone, I met this issue today and would like to ask if this feature is going to be released soon or not? Thank you for all your works.

That'll would help a lot here too!!

I want to run the validation once a specific value is set. With the current implementation it's either for every fields or at the form's submission.

Any update on this?

I need callback too, after state update has been applied, to trigger the auto-save on one of the forms. The form i am working on is long and massive i.e. split into multiple UI cards. As it is long, user should be able to pause/resume it, which created the need of auto-saving the data so that user can come back and resume the form.
Currently, I am using the validate function as a hook to save changes but it has other problems like validating the whole form even when it's not needed. So it will be great if we can pass a callback to SetFieldValue to hook into the state update life cycle.

This is what I do:

onClick={async () => {
                  await setFieldValue("is_active", false);
                  submitForm();
                }}

+1 callback for setSubmitting and resetForm.

@junaidmanzur I created a component similar to this to store the form values on change.. Not the ideal solution but it's working until this issue gets closed.

@jaredpalmer This would also solve the situation with multiple submit buttons as you would wait for the promise to resolve and after that submitForm

Seems like there is a enough requests here. Let me look into this.

To synthesize the above, the goal is to respond to a set value event inline (so as to use the updated state) somehow.

Hi @jaredpalmer any updates on this feature?

I have added a PR, adding promise to the setFieldValue
#1398
@jaredpalmer can you take a look?
thanks

I have met this problem today, I`m waiting for PR but now use that code below

const asyncSetFieldValue = async (nameField, value) => {
    await setFieldValue(nameField, value);
    handleSubmit();
  };

This is what I do:

onClick={async () => {
                  await setFieldValue("is_active", false);
                  submitForm();
                }}

FYI anyone has issue on Safari: use onClick is working fine on both Safari rather than using onFocus

Copying my answer from this SO question - https://stackoverflow.com/questions/56613496/issue-with-values-formik/57686391#57686391

If I understand the question correctly, we need a reliable way to get the latest state of Formik values, right after setting a Field's value & trigger another method/function/event based on this 'new' set of values. The obvious hindrance here is that setFieldValue() is an asynchronous method that internally uses setState() & hence there is no direct way to achieve this in a way that is described / expected by the OP.

  • I wouldn't recommend the workarounds (setTimeout, random await just to bypass the nextTick, etc) mentioned above, since they are non-deterministic.
  • One way that worked for me is using componentDidUpdate (or any other comparable React Lifecycle method) and listening for change in the props.
  • Formik exposes the field values in React component's props at this.props.values
  • When you use componentDidUpdate, you just would need to compare the previous props to new props to check if something has changed.

Here is a minimal code to show what I mean.

    const _ = require('lodash');

    class Sample extends React.Component {
      componentDidUpdate(prevProps) {
        if(!_.isEqual(prevProps.values, this.props.values)) {
          // At least one of the Formik fields have changed. And this.props.values holds this newest data about of the fields.
          triggerWhateverWithNewValues(this.props.values);
        }
      }
    }

Think it would solve the problem core problem mentioned in this issue ?

@royzer I agree that it solves the problem as a workaround, but it doesn't resolve the underlying issue being tracked here. For a nice business logic implementation, one wants to write:

await setFieldValue('sendThisToServer', 42);
await submitForm();

This makes it obvious that the code does a form submit, as opposed to storing some magic values somewhere and then doing the form submission inside componentDidUpdate. Besides making the code shorter and more understandable, it also allows using the local variables before/after submitForm without worrying about transferring them into componentDidUpdate.

Furthermore, suppose the code between and around these two lines is actually quite complicated (looking up data, doing some validation, and so on) and you might expose it as a separate function to a unit test, whereby the unit test just injects mocks for setFieldValue and submitForm -- you couldn't do that if all of this depended on the React component lifecycle.

I came across this wondering why my wrapped component is not validating on change, even though the touched field has been set to true. this is what I came up with based on the answers here.

import * as React from 'react';
import { Field, useFormikContext } from 'formik';
import { DatePicker } from 'antd';

const FormDatePicker = ({
  name,
  validate,
  onChange = () => {},
}) => {
  const { validateField } = useFormikContext();
  return (
    <Field name={name} validate={validate}>
      {({
        field: { value },
        form: { setFieldValue, setFieldTouched },
        meta
      }) => (
        <>
          <DatePicker
            value={value ? moment(value) : undefined}
            onChange={async (date, dateString) => {
              // workaround: https://github.com/jaredpalmer/formik/issues/529
              await setFieldValue(name, date);
              await setFieldTouched(name, true);
              onChange(date, dateString);
              validateField(name);
            }}
          />
          {meta.error && <p>{meta.error}</p>}
        </>
      )}
    </Field>
  );
};

export default FormDatePicker;

@royzer I'm using typescript, where props are strongly typed in an interface and a functional component. So I'm not sure how I can listen for changes in formik props.

I have met this problem today, I`m waiting for PR but now use that code below

const asyncSetFieldValue = async (nameField, value) => {
    await setFieldValue(nameField, value);
    handleSubmit();
  };

This doesn't make sense if you only want to udpate values and not submit the form.

Just as an update y’all, this is not possible with hooks since useState’s updater fn no longer accepts a 2nd argument callback anymore. The “correct” approach with hooks is do this with useEffect. This sucks, because you may not be able to figure out from where the change came from, but it is only way to do this.

could you give an example of how to do it with useEffect?

Would love an example too please.

@lukas1994 @MakhouT minor example:

import React, { useEffect } from 'react';
import { useFormikContext } from 'formik';

const { values, handleChange } = useFormikContext();

useEffect(() => {
 // At this point firstName it's updated with the lastest user input
}, [values.firstName]);

<input onChange={handleChange} name="firstName" ... />

It is less elegant than this (IMO):

const { values, setFieldValue } = useFormikContext();

const onInputChange = ({ target: { name, value }}) => {
  // Input value here it's updated with the lastest user input
  setFieldValue(name, value);

  // values.firstName it'sn't updated thats why we need useEffect
  console.log(values.firstName)
}

I do not know if the example will serve you, even so, good luck! :rocket:

If the functionality is really important to the Input instead of the form, I'd use a custom input for this purpose. I wonder if there's any value to expose this on <Field />.

(FYI, I'm still on v1 but I'm vaguely familiar with v2, so I may have some of the API wrong below)

const MyInput = (props) => {
    React.useEffect(() => {
        if (props.onValueChanged) {
            props.onValueChanged(props.field.value);
        }
    }, [props.field.value, props.onValueChanged]);

    return <input 
        onChange={props.field.handleChange} 
        name={props.field.name} 
        // etc
    />
};
const MyForm = () => {
    const onValueChanged = React.useCallback((value) => { console.log('changed!', value) }, []);
    return (
        <Formik initialValues={{ myValue: '' }}>
            <Field as={MyInput} name="myValue" onValueChanged={onValueChanged} /> 
        </Formik>
    );
};

This thread is confusing (for me). Looking at the code, setFieldValues actually does return a promise. Is this discussion related to v1 only? Can I now implement multiple submit buttons with

onClick={() => {
  await setFieldValue('buttonUsed', 'button1');
  submitForm();
}

or do I still need some setTimeout hacks? Thanks.

@ondrejpar it's very complicated. Basically, setFieldValue will wait for any asynchronous actions to happen (I believe), but even if this happens, your version of submitForm may have stale values without a setTimeout because React may not commit the render which updates the underlying callback.

I am working on an experimental PR which will create stable references to submitForm and other imperative methods. #2931

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pmonty picture pmonty  Â·  3Comments

PeerHartmann picture PeerHartmann  Â·  3Comments

outaTiME picture outaTiME  Â·  3Comments

sibelius picture sibelius  Â·  3Comments

dearcodes picture dearcodes  Â·  3Comments