formik: Can't call setState (or forceUpdate) on an unmounted component.

Bug, Feature, or Question?

All of the above. Maybe there is a better way to do what I’m trying to do, which is wrap a material-ui component with a formik Field. =) In my own code, I use a nice re-usable component and TypeScript. I tried to simplify things here.

Please see the codesandbox: https://codesandbox.io/s/21jowlv4p

  1. Click the button.
  2. Type some text.
  3. Hit return.
  4. Look in the console.

Since it is timing related (depends on when the promise executes), if you don’t see the error, click the reload button in the sandbox and try again.

Current Behavior

There doesn’t seem to be an easy way to hook into the promise of validateYupSchema. It resolves after the modal is closed and therefore the component has already unmounted and the setState call fails.

Using a promise like this doesn’t seem like a good idea since promises really should be chained to ensure execution order. Given that onSuccess() could be null as well, that also seems dangerous.

validateYupSchema(values, schema).then(
      () => {
        this.setState({ errors: {} });
        if (onSuccess) {
          onSuccess();
        }
      },
      (err: any) =>
        this.setState({ errors: yupToFormErrors(err), isSubmitting: false })
    );

https://github.com/jaredpalmer/formik/blob/master/src/Formik.tsx#L390

Thus, this error:

Warning: Can’t call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

Desired Behavior

No errors.

Suggested Solutions

I’m not quite sure yet. I’d say start with doing that promise in another way.

Additional Information

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 23
  • Comments: 22 (13 by maintainers)

Commits related to this issue

Most upvoted comments

I have the same type of problem. I’m using a form in a popup that I close in my submitHandle’s function success callback.

The workaround I have for now is to delay closing the popup for 200 ms in addition to preventing validation on blur as @lookfirst mentionned. Not pretty, but avoids the error for now. I’d be glad to have a more permanent fix, since validation on blur is quite useful for UX.

const formikOptions = {
  mapPropsToValues: () => defaultValues,
  validationSchema: schema,
  handleSubmit({ name }, { props, resetForm }) {
    myMethod({ name }, uiCbHndl({
      onSuccess: () => setTimeout(props.closePopup, 200),
      onEnd: () => resetForm(),
    }));
  },
  validateOnBlur: false,
};

It’s because the yup validations are done in a promise and when the promise resolves it calls setFormikState which calls this.setState(s, callback); without checking if the component is still mounted: https://github.com/jaredpalmer/formik/blob/0b8346d302e71515b8cd987e528908003e0145f6/src/FastField.tsx#L225

I’ve solved my problem using this:

state = {
    isMounted: false,
  };

componentDidMount() {
    this.setState({ isMounted: true }, () => {
      if (this.state.isMounted) {
        this.setState({ isMounted: false });
        {
          // do something
          // this.props.onClick(...)
        }
      }
    });
  }

I’m seeing this when trying to navigate using react-router-dom in handleSubmit. If I put history.push in a setImmediate callback I can get around the problem. It almost seems like we need an afterSubmit handler or something.

fxiing this as we speak.

you’re true, it was time for me to sleep yesterday :-p it makes no sense to async handleSubmit.

I had done some other modifications between to do this (I haven’t refreshed my window) 😕 …

You can solve this with doing your SetState with an arrow function.

  handleSubmit: (payload, { props }) => {
    props.closeWindow();
  },

and in your parent component :

closeWindow() {
 return () => this.setState(....)
}

personnally I have solved this error message with writing handlesubmit like that

  handleSubmit: async (payload, { props }) => {
    await console.log(payload);
    props.closeWindow();
  },

The more I think about this, the more I wish onSubmit() returned a promise that resolved after all the internal formik stuff resolved. onSubmit( setState() ).then( closeModal )