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.

componentWillUnmount is lazy?

See original GitHub issue

I’m turning here because I ran into this issue and I have real difficulty explaining it. I must admit I don’t know what I am doing exactly because I am porting someone else’s work and tests, but it was supposed to be an easy port and is turning out… not so easy… I am not using preact-compat because I feel a true port should not need it.

I pushed my work so far so you can see what I’m doing: https://github.com/Download/preact-helmet/tree/port-to-preact

The problem: I have a test failing in preact-helmet and I can’t explain it. It looks like componentWillUnmount is called later than expected by the test. See the Travis run.

In the test I am forcing an unmount by rendering an EmptyComponent. This is based on remarks from the issue tracker here and from preact-compat.

In each test, I render using this code:

child = render(
  <Jsx>Bla</Jsx>
  ,
  container, child
);

Here, container is a div nested in the document body, that is never replaced. Child is initially undefined and then the result of each render call.

In an after hooks that runs after each test, I call unmountComponentAtNode, which I have tweaked slightly from the one found in preact-compat:

function unmountComponentAtNode(container, child) {
    return render(<EmptyComponent />, container, child);
    function EmptyComponent() { return null; }
}

Analysis so far: I debugged this the ugly way: by placing console.info statements in the code.

Firs I placed a log statement in unmountComponentAtNode. That confirms that this function is indeed called after each test.

Next I placed two log statements in preact-side-effect, that this module depends on. I just modified the local code that can be found in ./node_modules/preact-side-effect/lib/index.js. One log statement in componentWillMount and one in componentWillUnmount. The one in componentWillMount also logs the new count (it is keeping track of a stack of mounted instances). Here is the code for completeness:

      SideEffect.prototype.componentWillMount = function componentWillMount() {
        console.info("SideEffect.componentWillMount: mountedInstances.length=", mountedInstances.length + 1);
        mountedInstances.push(this);
        emitChange();
      };

      SideEffect.prototype.componentWillUnmount = function componentWillUnmount() {
        console.info("SideEffect.componentWillUnmount");
        var index = mountedInstances.indexOf(this);
        mountedInstances.splice(index, 1);
        emitChange();
      };

The weird thing is that at the start of a new test, if I call SideEffect.peek(), it still gives me the state from the last mounted instance. And indeed I see in the logs that at this point, componentWillUnmount has not been called yet (even though unmountComponentAtNode has). If I then proceed to render again in this new test, componentWillUnmount is called just before the new node is mounted. Here is the log output:

Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
        √tags without 'src' will not be accepted
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'SideEffect.componentWillUnmount'
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'SideEffect.componentWillMount: mountedInstances.length=', 1
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'SideEffect.componentWillMount: mountedInstances.length=', 2
        √will set script tags based on deepest nested component
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'SideEffect.componentWillUnmount'
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'SideEffect.componentWillUnmount'
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'SideEffect.componentWillMount: mountedInstances.length=', 1
        √sets undefined attribute values to empty strings
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
        √won't render tag when primary attribute (src) is null
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
        √won't render tag when primary attribute (innerHTML) is null
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
      noscript tags
        √can update noscript tags
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
        √will clear all noscripts tags if none are specified
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
        √tags without 'innerHTML' will not be accepted
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
        √won't render tag when primary attribute is null
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
      style tags
        √can update style tags
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
        √will clear all style tags if none are specified
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
        √tags without 'cssText' will not be accepted
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
        √won't render tag when primary attribute is null
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
    misc
      √throws in rewind() when a DOM is present
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
      √lets you read current state in peek() whether or not a DOM is present
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
      √will html encode string
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
      √will not change the DOM if it is receives identical props
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
      ×will only add new tags and will preserve tags when rendering additional Helmet instances
        AssertionError: expected { Object (linkTags) } to have a property 'metaTags'
            at Context.<anonymous> (webpack:///lib/test/HelmetTest.js:9:77606 <- lib/test/HelmetTest.js:235:77628)

Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
      √can not nest Helmets
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
      √will recognize valid tags regardless of attribute ordering
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'unmountComponentAtNode: child is defined'
Chrome 56.0.2924 (Windows 10 0.0.0) INFO LOG: 'SideEffect.componentWillMount: mountedInstances.length=', 1

(sorry about the noise, that’s Karma)

The last test is the failing one. I’m showing all this logging because it shows how other tests (that are doing rendering) actually successfully mount/unmount… which makes this one test so weird. Also notice how the length reported from componentWillMount (which is the new length after mounting) is correct. Instances are not piling up. Also notice how componentWillUnmount is called just before the new one is mounted, even though unmountComponentAtNode has sometimes been called multiple times in between.

I have been staring at this for hours now and can’t figure out what is happening so a second pair of eyes would be really appreciated!

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:12 (12 by maintainers)

github_iconTop GitHub Comments

2reactions
developitcommented, Apr 6, 2017

Fixed in 8.

1reaction
developitcommented, Feb 9, 2017

Let’s keep this issue open, but I’ll be fixing #538 first - after that’s merged we’ll check if this still needs separate work.

Read more comments on GitHub >

github_iconTop Results From Across the Web

componentWillUnmount is lazy? · Issue #534 · preactjs/preact · GitHub
In the test I am forcing an unmount by rendering an EmptyComponent . This is based on remarks from the issue tracker here...
Read more >
componentWillUnmount is not getting invoked - Stack Overflow
I have a backbone application, where i am trying to integrate React components. React component is mounted using following code : ReactDOM.
Read more >
How to use componentWillUnmount in Functional ...
In a nutshell, how to properly use componentWillMount and componentWillUnmount in a functional component with a useEffect in React.
Read more >
React: Stop checking if your component is mounted - Medium
But the proper solution is cancellation. Or ignoring if you're lazy about cancelling. Original story: As with any other React application, we ...
Read more >
componentWillUnmount in React | Lifecycle Methods - YouTube
Component Lifecycle Methods | componentDidMount | componentWillUnmount | constructor | React Tutorials----- SUBSCRIBE TO CHANNEL ...
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