Preact: 10.0.0-beta.1 componentWillUnmount does not fire when using Preact.render() with replaceNode

Created on 22 May 2019  路  5Comments  路  Source: preactjs/preact

When calling preact.render() with the third optional replaceNode argument, re-rendering over a component does not call the componentWillUnmount lifecycle hook.

Consider the HTML:

<div id='app'>
  <div id='component'></div>
</div>

I want to change what component is rendered in the #component div, and because our app is not 100% preact, we use a separate call to preact.render to swap out the components.

First render ComponentA:
render(<ComponentA />, document.getElementById('app'), document.getElementById('component'));

Resulting in:

<div id='app'>
  <div id='component'>I am ComponentA</div>
</div>

Sometime later render over ComponentA with ComponentB:
render(<ComponentB />, document.getElementById('app'), document.getElementById('component'));

Which does actually render correctly resulting in:

<div id='app'>
  <div id='component'>I am ComponentB</div>
</div>

But ComponentA did not call componentWillUnmount before it was overwritten. In my specific case this causes a setInterval to go uncleared, and results in some strange behavior when that interval continues to be called.

I have a codesandbox which shows the behavior more clearly: https://codesandbox.io/s/preactwillunmountbug-fi1d5

If you open a console and click the Overwrite clock doesn't call willUnmount button you will see the interval continue to log statements into the console. At first the clock isn't present, and then it gets appended next to the other placeholder component, I think as a result of the call to setState inside the interval.

If you instead click the Unmount clock calls willUnmount button you will see the unmount functional runs and the interval is cleared.

It was my expectation that both of these methods of overwriting a component with another should result in the same behavior.

bug

All 5 comments

@LukasBombach I saw your nicely documented PR here https://github.com/developit/preact/pull/1557

It seems like we are using very similar use cases, I was wondering if you are rendering with something similar to this strategy? I thought you might have some good thoughts since you were just in this code and have a similar use case! Thanks!

Hey,

First, I want to thank you for your thorough research and comprehensive reproduction.
I have looked into this a bit and found that the problem is probably caused here: https://github.com/developit/preact/blob/master/src/render.js#L24

When using replaceNode we disregard the previous dom meaning our diffing can't know what to unmount and what it shouldn't.

I'll work on this tonight.

@1campbellj I am currently writing on a lengthy medium article about this which I hope to publish today. Along with the article I have implemented a working proof of concept that does partial hydration with preact and next.

You can already check out the repo here: https://github.com/spring-media/next-super-performance

Nice fix

Was this page helpful?
0 / 5 - 0 ratings