formik: Set Field Value not working when called from didmount

Current Behavior

When I call setFieldTouched and setFieldValue in the componentDidMount method, the value is not applied in the values object but in the touched object it is.

Steps to Reproduce

Expected behavior

1 Create a form with formik, it can be with the Form component or the withFormik hoc. 2 Call setFieldValue in the componentDidMount and check the values prop for any change.

Suggested solution(s)

Seems like in some way the form is not ready to change values, since a workaround could be to use the setState callback or setTimer( () => this.props.setFieldValue(), 0 )

Additional context

This happens in 1.0.3 since in 1.0.2 seems to work fine

CodeSandbox Link:


  • Formik Version: 1.3.0
  • React Version: 16.5.1
  • TypeScript Version: na
  • Browser and Version: Chrome 69.0.3497.100
  • OS: MacOs High Sierra 10.13.6
  • Node Version: 10.8.9
  • Package Manager and Version: yarn 1.9.4

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 19
  • Comments: 25 (1 by maintainers)

Most upvoted comments

☝️ Stale? I’ve just encountered this issue and it’s pretty annoying, any fix in sight? 😕

@ericbiewener I can’t seem to find that a solution… right now using formik in a more complex way with nested dynamic fields is not possible.

and @AndreKelling I wouldn’t recommend on downgrading to 1.0.2 because it is before the performance fixes.

For anyone encountering this bug, I’ve found a workaround, it’s not pretty but it works. If our problem is that formik won’t initiate any updates until it is mounted, I used setTimeout with 1msec to make sure what I want happens inside componentDidMount and after formik IS mounted.

image

More broadly, you can’t call any of the FormikBag functions that modify the formik state, e.g. form.setSubmitting(). This is because the didMount flag in the <Formik> component doesn’t get set to true until componentDidMount, but componentDidMount is called for child components before the parent.

I’m not sure what a good solution is here. Adding didMount to the formik state would allow child components to get notified of when the parent form has mounted, but this feels messy. Perhaps the FormikBag could include an onMount() method that takes a function as its sole argument. That function in turn would get called in the componentDidMount method of <Formik>

The same issue, it’s not possible to populate dynamically form with some values inside componentDidMount method.

How you would want to get this working in componentDidMount lifecycle method without using setTimeout is to create an async method which calls the setFieldValue.

componentDidMount() {
   initValues()
}

async function initValues() {
   return await setFieldValue("field", value)
}

We are ultimately taking the approach I suggested in my previous comment. We already had a Form class component that wrapped the main Formik component. We have now added the following methods/properties:

class Form extends React.Component {
  
  onMountCallbacks = [];

  componentDidMount() {
    if (this.onMountCallbacks.length) {
      for (const fn of this.onMountCallbacks) fn();
    }
  }

  registerOnMountCallback = fn => {
    this.onMountCallbacks.push(fn);
  };

We then pass down onMount: this.registerOnMountCallback as an additional property on the usual FormikBag in the form’s render method.

Forgive me if I’m not quite understanding the use-case of setting the value in the componentDidMount method, but the implementation of setting values in the mount phase seems a little bit like an anti-pattern.

Typically, if I’m initializing the form, I leverage two props provided by Formik: initialValues and enableReinitialize. I move the initialValues state up to the parent component so that I can pass it as props down to the form component. enableReinitialize will allow the form to reset the initialValues once they change in the parent component (either through an asynchronous network request or simply by setting the state). If you’re not setting the form dynamically through an async request, I would strongly suggest simply just setting the initial values statically as opposed to using setState in the mount phase (this will avoid an unnecessary additional render).

Now, while this solves the values issue, it doesn’t solve the touched issue. My only work-around for that is using componentDidUpdate to detect when the value populates in the form, then you can use the setFieldTouched

componentDidUpdate(prevProps) {
  if (prevProps.values.sample === '' && this.props.values.sample) {
    this.props.setFieldTouched('sample');
  }
}

I forked the first sandbox posted and altered it for this specific example. https://codesandbox.io/s/formik-example-8se6d

I’m not implying this isn’t a bug–I’d have to look at the code a little bit deeper to understand what’s happening, but I’m just suggesting a different approach that might avoid some other pitfalls.

I hope I understood the use-case enough and that this answer is both relevant and helpful!

Had to run a validateForm() inside a componentDidUpdate, but kept validating using the previous values. Wrapping it in a setTimout did the trick.

  componentDidUpdate(prevProps) {
    const {fileId, validateForm} = this.props;
    if (prevProps.fileId !== fileId) {
      setTimeout(() => {
        validateForm();
      }, 0)
    }
  }

Same problem, can’t set field value on componentDidMount. I need to sync some form fields with redux store, but I can’t use mapStateToProps because it will reinitialize whole form… @jaredpalmer is possible to do something with this issue? Thanks

Same problem here with setFieldValue since 1.3.0 and the suggested workaround works for me as well, but I totally agree that it’s hacky.

I looked for the cause for the problem and this PR seems to be it: #895
Is this really wanted behaviour to prevent changing the state before the whole form component would be mounted? Thanks.

In React Native this worked for me:

componentDidMount() {
    InteractionManager.runAfterInteractions(() => {
      this.props.setFieldValue('test', 123);
    });
}

I am also experiencing it with the connect() method. Only after I handle it with setTimeout(someFunction, 0) it works normally. But I don’t like it since it seems “hacky”.