Skip to content

Fix wrong DOM order on suspend inside Fragment#2107

Merged
marvinhagemeister merged 13 commits into
preactjs:masterfrom
jviide:suspense-mixup
Nov 12, 2019
Merged

Fix wrong DOM order on suspend inside Fragment#2107
marvinhagemeister merged 13 commits into
preactjs:masterfrom
jviide:suspense-mixup

Conversation

@jviide

@jviide jviide commented Nov 10, 2019

Copy link
Copy Markdown
Contributor

This pull request fixes issue #2106 and refactors the Suspense implementation for smaller code size.

  • Modify the Suspense lifecycle to be more explicit:
    • On the first suspended child set the _detachOnNextRender flag and force a new render with setState.
    • On the next render detach the old suspended children from DOM and replace them with an empty list of children.
  • Use a counter to keep track of the unresolved promises instead of an array of the promises.
  • Add a test for issue Suspense inside a Fragment may scramble the DOM order #2106.
  • Fix Suspense test "should correctly render nested Suspense components", it didn't return a promise.
  • Add a test for children suspending with the same promise.
  • Add a test ensuring that no children will be rendered if any of them suspends.

The changes in this PR bring down the compat.js.gz bundle size by 38 bytes (from 2700 B to 2662 B).

Fixes #2106.

@coveralls

coveralls commented Nov 10, 2019

Copy link
Copy Markdown

Coverage Status

Coverage decreased (-0.0009%) to 99.891% when pulling a436186 on jviide:suspense-mixup into 3d2edb6 on preactjs:master.

@jviide jviide changed the title Add a test illustrating issue #2106 Fix #2106 Nov 10, 2019
Comment thread compat/src/suspense.js
Comment thread compat/src/suspense.js Outdated
Comment thread compat/test/browser/suspense.test.js

@sventschui sventschui left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you probably already saw I am super busy the past weeks and didn't spend that much time on preact. I nevertheless wanted to drop some comments here as I find this PR super interesting.

The cave-at with the suspensions/Promise de-duplication should be looked at and I'm super happy to chat here or on slack if you have any questions. Also I try to dig up my de-duplication work and see where this went if I can find some spare minutes.

Anyways thank you for having a look at improving the current implementation :)

Comment thread compat/src/suspense.js
Comment thread compat/src/suspense.js
Comment thread compat/src/suspense.js Outdated
Comment thread compat/src/suspense.js
Comment thread compat/src/suspense.js

/** @jsx createElement */

async function waitResolved(suspense) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In another branch I'm rewriting tests that rely on internals. If you are interested, I rewrote these tests to no longer rely on _suspensions if you'd like to pull that change in :)

fa922cb

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet! Sorry that I didn't react to this in time. If this PR caused merge conflicts to your branch I can create a patch to resolve them.

Comment thread compat/src/suspense.js
Comment thread compat/src/suspense.js
Comment thread compat/src/suspense.js
@jviide

jviide commented Nov 12, 2019

Copy link
Copy Markdown
Contributor Author

Updated the implementation. Now DOM is not modified manually at all. Instead the suspended component is removed/detached/lifted from the rendered tree to keep the component's state intact, and added back to the rendered tree when the suspension gets resolved.

The [<Fragment>{props.children?}</Fragment>, props.fallback?] structure that render returns is there because it makes bookkeeping easier: We always know that the (potentially suspended) children are inside the first child of the Suspense component.

This brings the compat.js.gz bundle size further down by 7 bytes (now 2655 B vs. the 2700 B in the master branch, 45 B reduction in total).

@marvinhagemeister marvinhagemeister left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So good!! 🙌 ❤️

@cristianbote cristianbote left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉💪👍 pure magic

@marvinhagemeister

Copy link
Copy Markdown
Member

Merging, coveralls is acting up strangely again...

@marvinhagemeister marvinhagemeister changed the title Fix #2106 Fix wrong DOM order on suspend inside Fragment Nov 12, 2019
@marvinhagemeister marvinhagemeister merged commit f45b611 into preactjs:master Nov 12, 2019
@sventschui

Copy link
Copy Markdown
Member

maybe coveralls is right. There are some missed branches in the suspense impl. Might be worth looking at these. I might find some time to do so later today

@andrewiggins

Copy link
Copy Markdown
Member

Note, if you click on the "coverage" badge you can see exactly which lines and branches aren't covered

@marvinhagemeister

Copy link
Copy Markdown
Member

There were no coverage changes related to any files in this PR. The one were coverage went down was the entry file of preact/debug 🤷‍♂️

@prateekbh

Copy link
Copy Markdown
Member

One problem: Anything relying on props.children now gets additional nodes due to the two children.

Anything traversing props.children will probably break. Unless thats an anti pattern for libraries that I am not aware of

@jviide jviide deleted the suspense-mixup branch November 13, 2019 02:06
@jviide

jviide commented Nov 13, 2019

Copy link
Copy Markdown
Contributor Author

@prateekbh props.children does not get modified by this code. Could you give an example of a case that you suspect might break?

@prateekbh

Copy link
Copy Markdown
Member

You're right I mistakenly added a ghost space in my tests and thought it came because of this

@prateekbh

Copy link
Copy Markdown
Member

All good 👍👍👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suspense inside a Fragment may scramble the DOM order

9 participants