question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

FormSpy: re-mounting subscription memory leak from render-phase side-effects

See original GitHub issue

Are you submitting a bug report or a feature request?

Bug report

What is the current behavior?

The FormSpy constructor has a side-effect which, when using React.StrictMode or React async mode, causes a subscription memory leak.

React.StrictMode helps detect unexpected side effects within the render phase by invocating some (most) of the React lifecycle methods twice - this includes the constructor. (See here for details/clarification). The FormSpy constructor runs the following (here):

this.subscribe(props, (state: FormState) => { ...

which in turn runs (here):

subscribe = (
    { subscription }: Props,
    listener: (state: FormState) => void
  ) => {
    this.unsubscribe = this.props.reactFinalForm.subscribe(
      listener,
      subscription || all
    )
  }

hence setting the this.unsubscribe class-member variable. Because the constructor is ran twice, the first instantiation’s unsubscribe value is overwritten, and only the 2nd call’s is ever executed in componentWillUnmount (here).

According to React’s docs on async rendering, render phase lifecycles may be invoked more than once, and so ~should~ can not have side-effects.

Because the above methods might be called more than once, it’s important that they do not contain side-effects. Ignoring this rule can lead to a variety of problems, including memory leaks and invalid application state.

What is the expected behavior?

All render-phase lifecycle methods should be side-effect free, allowing for compatibility with React.StrictMode and React async rendering mode.

Sandbox Link

https://codesandbox.io/s/n1p24n90xj It’s a little difficult to “show” the issue, but this will do it.

  1. Open & clear the Console
  2. Add 1 letter to the end of either field (e.g. John -> Johna
  3. Check the console and you’ll see “Values changed” twice - This is because the constructor was invocated twice, leading to 2 subscriptions.
  4. Click the “Re-mount FormSpy” button & clear the console
  5. Add 1 letter to the end of either field (e.g. Doe -> Does)
  6. Check the console and you’ll see “Values changed” three times - The FormSpy has re-mounted, hence invocated componentWillUnmount (commit phase) once and the constructor (render phase) twice. One of the original mounting’s subscriptions has been retained and not been unsubscribed from during unmounting.

The more you re-mount the FormSpy, the more subscriptions are left over. Mount it 10 times (so the button says “Mounted 10 times”) and you’ll see “Values changed” 11 times on every field change (9 stale subscriptions which should have been removed, and 2 new subscriptions).

What’s your environment?

  • Final Form v4.11.0
  • React-Final-Form v4.0.2
  • React v16.5.2 (or any version with React.StrictMode, or any version with async rendering enabled)

Other information

Although the codesandbox example is non-real-world, we hit this problem in a real-world scenario where our form expands revealing extra form Fields and FormSpys.

I am more than happy to work on this and PR it, but will obviously needs plenty of review. It will also be a breaking change, as the subscription (and subsequent onChange being called) will be moved from the constructor to componentDidMount

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:2
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
erikrascommented, May 15, 2019

This should be fixed in v5. Reopen if not.

1reaction
callmeberzerkercommented, Apr 15, 2019

In the mean time…

if (process.env.NODE_ENV !== 'production') {
  const pattern = /is deprecated and will be removed in the next major version/;
  const { warn } = console;
  console.warn = function warnWithoutReactViolations(message, ...rest) {
    if (!pattern.test(message)) {
      warn(message, ...rest);
    }
  };
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to fix the React memory leak warning
This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a...
Read more >
Final Form Docs – `<FormSpy/>`
A component that subscribes to form state, and injects both form state and the form instance via a render prop. The <FormSpy/> will...
Read more >
Release 0.5.1 - GNU MediaGoblin's documentation!
Mounting MediaGoblin itself via FastCGI. location / { ... If the contents gets leaked nevertheless, delete your file and restart the server, ...
Read more >
Hunting memory leaks in a server side rendered React ...
Our memory leak was caused by reselect and with the bad usage of styled-components, both problems were found by using Chrome DevTools. Backstory....
Read more >
Untitled
Plasma ghosting effect, Formation des chaines de l'himalaya, Partido peru argentina en vivo hd. Puutarhaan pistorasia, Max steel todos los elementors, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found