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.

render function from preact X behaviour diff from preact 8.x and why ?

See original GitHub issue

upgrade from 8.x to preact-10.1.0, my code work not as expected , and it turns out that each time call

  import { render } from 'preact'  // preactX or preact8.x
  render(<MyComponent />, targetDOM),  

MyComponent will fire constructor in preact X and initialize all components, however preact 8.x is not like that … … preact8.x 's behaviour is more like render at first-time and then always re-render

i do know preactX.render has remove the third argument in render method, maybe problems come from here ?

am i missing something in the doc ? it brothers me quite a lot …

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

10reactions
marvinhagemeistercommented, Dec 15, 2019

Thanks for providing more information! The codesandboxes really help 👍

You’re right that in Preact X a new key value will forcefully unmount and mount the component again. This is in fact an intended behavior as it solved some unexpected ordering bugs that would pop up with the 8.x release line.

keys are not meant to be set to random numbers or counters or whatever. That erases all their advantages. But to understand that we have explain why keys are needed in the first place. Take this example, where mounting/unmonting both A and B is considered expensive:

// First render
<div>
  <div>
    <A />
  </div>
  <div>
    <B />
  </div>
</div>

// 2nd render, children are moved around
<div>
  <div>
    <B />
  </div>
  <div>
    <A />
  </div>
</div>

Whenever we compare the children we can skip a lot of unnecessary work by detecting if some elements just moved around. If we can’t detect that, we need to unmount the old components and mount the new ones. Now the trouble with that detection is that we need some way to know if the element in the 2nd render matches another in the first render. But because each render will create new objects, we can’t just do a reference comparison.

function Foo() {
  return { type: "div" }
}

// Will always return false, because each function
// invocation will create a new object
console.log(Foo() === Foo());

So, we need another way to match the old elements with the new ones. And this is where keys come in! They act as an identifier for each child. Let’s rewrite the above example:

// First render
<div>
  <div key="A">
    <A />
  </div>
  <div key="B">
    <B />
  </div>
</div>

// 2nd render, children are moved around
<div>
  <div key="B">
    <B />
  </div>
  <div key="A">
    <A />
  </div>
</div>

Now we can easily determine if we need to re-create an element from scratch, or if we just need to move it to a different addition. That’s a lot less costly than creating a bunch of elements that are already there. This is possible thanks to the key values. They are nothing more than a simple id meant to tag a specific child.

This leaves open the question, why we don’t reuse the element, when it is of the same type? This line of thought makes totally sense and was the way 8.x worked. Troubles arise as soon as you look deeper down and take the child and the whole tree that belongs to it into account. If we ignore the keys and just matched based on type, we’d pay a pretty hefty cost for that in this scenario. Behind a children may lie another component that’s pretty expensive to render and because we’ve re-used the wrong parent, we may need to completely destroy the tree later. So we gained nothing, but made a huge loss instead.

Things get even more funny when you take the statefulness of components into consideration. Take this example:

<div>
  <Foo />  // state.value = 2
  <Foo />  // state.value = 10
</div>

If the order of both Foo components would switch around with the next render, we’d leave them as is without keys. Instead of the first component displaying an expected value of 10, it would still display 2, and that’s a pretty critical bug to have.

Conclusion

The behavior in 8.x lead to a lot of correctness bugs, which we fixed with X.

  • Always use keys when children are dynamic/moved around
  • Make sure that the key is stable (no Math.random(), counter)
  • Setting key to a different value on an existing element will unmount and re-mount it, no matter if the type is the same.
1reaction
zlv-thisFcommented, Dec 16, 2019

@marvinhagemeister amazing!~ thanks for your help !!!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Upgrading from Preact 8.x
In Preact X, render() always diffs DOM children inside of the container. So if your container contains DOM that was not rendered by...
Read more >
preact - npm
Fast 3kb React-compatible Virtual DOM library.. Latest version: 10.11.2, last published: 25 days ago. Start using preact in your project by ...
Read more >
Preact - Releases
4 or newer for server-side rendering. (#3583, thanks @JoviDeCroock). Fixes. Fix memory leak by cleaning up _parent , _dom and __hooks ...
Read more >
Build a SSR App With Preact, Unistore, and Preact Router
The idea is to create a Preact app as it were and hook it up to a Node server using the preact-render-to-string package....
Read more >
Strict Mode - React
Detecting unexpected side effects · The render phase determines what changes need to be made to e.g. the DOM. During this phase, React...
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