Skip to content

fix(refresher): avoid gesture setup after disconnect - #31316

Merged
ShaneK merged 4 commits into
ionic-team:mainfrom
ptmkenny:refresher
Aug 3, 2026
Merged

ShaneK merged 4 commits into
ionic-team:mainfrom
ptmkenny:refresher

Conversation

@ptmkenny

@ptmkenny ptmkenny commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Issue number: resolves #31315

What is the current behavior?

When an ion-refresher's host page is unmounted while the native refresher is still being set up, Ionic throws an uncaught TypeError from the gesture utility:

TypeError: Cannot read properties of undefined (reading '__zone_symbol__addEventListener')
    at addEventListener (core/src/utils/gesture/listener.ts:21)
    at Object.enable (core/src/utils/gesture/index.ts)
    at Refresher.disabledChanged (core/src/components/refresher/refresher.tsx:125)
    at Refresher.setupMDNativeRefresher (core/src/components/refresher/refresher.tsx:372)

setupMDNativeRefresher() and setupiOSNativeRefresher() both do:

this.gesture = (await import('../../utils/gesture')).createGesture({
  el: this.scrollEl!,
  ...
});

The dynamic import yields to the event loop. If the refresher is disconnected while it resolves, disconnectedCallback() (line 547-549) has already run this.scrollEl = undefined, so createGesture receives el: undefined, and the subsequent disabledChanged() → gesture.enable(true) calls addEventListener(undefined, ...), which throws.

Because this happens inside an async method that nothing awaits, it surfaces as an unhandled promise rejection — it cannot be caught by a framework error boundary; this crashes my app in playwright sometimes when components are mounted very quickly.

The ! non-null assertions on this.scrollEl are what hide this from TypeScript.

What is the new behavior?

If the refresher is disconnected while the gesture module is being imported, setup aborts quietly. No gesture is created, and no error is thrown.

Does this introduce a breaking change?

  • Yes
  • [ X] No

Other information

@ptmkenny
ptmkenny requested a review from a team as a code owner August 1, 2026 00:36
@ptmkenny
ptmkenny requested a review from ShaneK August 1, 2026 00:36
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

@ptmkenny is attempting to deploy a commit to the Ionic Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the package: core @ionic/core package label Aug 1, 2026
@ShaneK ShaneK changed the title fix unmount race in refresher fix(refresher): avoid gesture setup after disconnect Aug 3, 2026

@ShaneK ShaneK 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.

Thank you so much for your issue and pull request! I just had a few quest change requests and the rest looks good to me

Comment thread core/src/components/refresher/refresher.tsx
Comment thread core/src/components/refresher/refresher.tsx
Comment thread core/src/components/refresher/refresher.tsx
@vercel

vercel Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
Image ionic-framework Ready Ready Preview Aug 3, 2026 4:58pm

Request Review

@ShaneK ShaneK 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.

Awesome work! Looks good to me, thank you!

@ShaneK
ShaneK added this pull request to the merge queue Aug 3, 2026
Merged via the queue into ionic-team:main with commit d9b877f Aug 3, 2026
51 checks passed
@ShaneK

ShaneK commented Aug 3, 2026

Copy link
Copy Markdown
Member

Thanks for your contribution! This should be deployed this Wednesday with our usual patch release

HappyKnuckles pushed a commit to HappyKnuckles/ionic-framework that referenced this pull request Oct 3, 2026
Issue number: resolves ionic-team#31315 
---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

When an `ion-refresher`'s host page is unmounted while the native
refresher is still being set up, Ionic throws an uncaught `TypeError`
from the gesture utility:

```
TypeError: Cannot read properties of undefined (reading '__zone_symbol__addEventListener')
    at addEventListener (core/src/utils/gesture/listener.ts:21)
    at Object.enable (core/src/utils/gesture/index.ts)
    at Refresher.disabledChanged (core/src/components/refresher/refresher.tsx:125)
    at Refresher.setupMDNativeRefresher (core/src/components/refresher/refresher.tsx:372)
```

`setupMDNativeRefresher()` and `setupiOSNativeRefresher()` both do:

```ts
this.gesture = (await import('../../utils/gesture')).createGesture({
  el: this.scrollEl!,
  ...
});
```

The dynamic import yields to the event loop. If the refresher is
disconnected while it resolves, `disconnectedCallback()` (line 547-549)
has already run `this.scrollEl = undefined`, so `createGesture` receives
`el: undefined`, and the subsequent `disabledChanged()` →
`gesture.enable(true)` calls `addEventListener(undefined, ...)`, which
throws.

Because this happens inside an async method that nothing awaits, it
surfaces as an **unhandled promise rejection** — it cannot be caught by
a framework error boundary; this crashes my app in playwright sometimes
when components are mounted very quickly.

The `!` non-null assertions on `this.scrollEl` are what hide this from
TypeScript.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

If the refresher is disconnected while the gesture module is being
imported, setup aborts quietly. No gesture is created, and no error is
thrown.

## Does this introduce a breaking change?

- [ ] Yes
- [ X] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

---------

Co-authored-by: Shane <shane@shanessite.net>

This branch was successfully deployed

1 active deployment
Preview — c85d7b34 Deployed Aug 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: core @ionic/core package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: ion-refresher throws when its page unmounts during native refresher setup

2 participants