An effect that inserts iframes may run twice in Safari
See original GitHub issueReproduction
Here’s a codesandbox showing a Preact effect running twice in Safari: https://codesandbox.io/s/safari-duplicate-effects-b2lnf.
Chrome result | Safari result |
---|---|
The root cause appears to be Safari running pending promises when an iframe is inserted into the DOM. You can verify by running this code in Safari’s dev console:
// Inserting a div results in panda/monkey
defer = Promise.prototype.then.bind(Promise.resolve())
defer(() => console.log("🐵")); document.body.appendChild(document.createElement('div')); console.log("🐼")
🐼
🐵
// Inserting an iframe results in monkey/panda
defer = Promise.prototype.then.bind(Promise.resolve())
defer(() => console.log("🐵")); document.body.appendChild(document.createElement('iframe')); console.log("🐼")
🐵
🐼
As far as I can tell, the exact setup to reproduce is:
- A parent component that can have its state updated by a child component
- An effect that updates the parent component’s state
- Another effect that inserts an iframe into the DOM
The state update enqueues components for re-render, then the iframe insertion causes Safari to immediately kick off a re-render inside Preact’s current render loop. This happens before the current render has cleared effects arrays, so the effects are executed again.
The sandbox looks contrived, but I ran into this while integrating PayPal into a Preact app.
Steps to reproduce
Running the sandbox in Safari 13.1.2 should illustrate the problem. The same sandbox will show one less element in Chrome.
Expected Behavior
Preact should not run an effect with no dependencies twice.
Actual Behavior
Preact runs an effect with no dependencies twice.
Issue Analytics
- State:
- Created 3 years ago
- Comments:7 (4 by maintainers)
Top GitHub Comments
Filed as https://bugs.webkit.org/show_bug.cgi?id=235322.
@developit I was able to resolve this issue in Safari with the following option override:
Is this what you were suggesting (a double promise resolution)? I assume so but want to make sure I’m not missing something. Would this be something Preact would accept as a contribution? It’s a bit of an unusual one since this mechanism is presumably a bit more expensive than the current
requestAnimationFrame
implementation, and is only needed for an edge case behaviour in one engine.I also noticed this pattern is internally used to create a function for deferring updates (https://github.com/preactjs/preact/blob/c7f57db13af43b8cc3353a138639ad9dd9523e16/src/component.js#L181) — do you know if this could be causing any similar issues?
(Sorry for reviving a zombie issue a year after our last comment, at Shopify we got bit by this issue again 😛)