Skip to content

Instantly share code, notes, and snippets.

@dandoingdev
Created October 1, 2026 15:31
Show Gist options
  • Select an option

  • Save dandoingdev/d7a6e044bf006bdb633aa32aaeffd410 to your computer and use it in GitHub Desktop.

Select an option

Save dandoingdev/d7a6e044bf006bdb633aa32aaeffd410 to your computer and use it in GitHub Desktop.
Ionic Vue 9.0.6: deterministic late swipe cancellation reproduction

Ionic Vue: late swipe cancellation accesses a missing page

Download these four files into one directory, then run npm install and npm run dev with Node 22.12+ or a compatible newer release. Open the displayed local URL and click Reproduce.

With Ionic Vue 9.0.6, the result is TypeError: Cannot read properties of undefined (reading 'ionPageElement'). On Safari the wording is undefined is not an object.

This is a deterministic callback-level reproduction: it uses the real Ionic router and outlet, pushes a second page, verifies that a back destination exists, returns to the root, then delivers swipeHandler.onEnd(false) as a late cancellation callback. It deliberately models the event ordering rather than attempting to reproduce the physical gesture/animation race. No view-stack or router mocks are used.

After guarding the missing view/page in Ionic Vue's IonRouterOutlet cancellation branch, the result is PASS. Normal swipe cancellation must still hide the entering page when it exists.

<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><title>Ionic Vue late swipe cancellation</title></head>
<body>
<h1>Late swipe cancellation</h1>
<button id="reproduce">Reproduce</button>
<pre id="result">Ready</pre>
<div id="app" style="position: relative; height: 300px"></div>
<script type="module" src="/main.js"></script>
</body>
</html>
import { createApp, h, nextTick } from 'vue';
import { IonicVue, IonPage, IonRouterOutlet } from '@ionic/vue';
import { createRouter, createMemoryHistory } from '@ionic/vue-router';
import '@ionic/vue/css/core.css';
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/page1', component: { render: () => h(IonPage, { 'data-page': 'one' }, () => 'Page one') } },
{ path: '/page2', component: { render: () => h(IonPage, { 'data-page': 'two' }, () => 'Page two') } },
],
});
await router.push('/page1');
createApp({ render: () => h(IonRouterOutlet) })
.use(router)
.use(IonicVue, { animated: false, mode: 'ios' })
.mount('#app');
const settle = async () => {
await nextTick();
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
};
await settle();
document.querySelector('#reproduce').onclick = async () => {
const result = document.querySelector('#result');
try {
await router.push('/page2');
await settle();
const swipeHandler = document.querySelector('ion-router-outlet').swipeHandler;
if (!swipeHandler.canStart()) throw new Error('Expected a back destination');
await new Promise(resolve => {
const removeHook = router.afterEach(() => { removeHook(); resolve(); });
router.back();
});
await settle();
// Deterministically deliver the cancellation callback after navigation has
// returned to the root. This models the late animation callback; it does
// not rely on reproducing the physical touch/animation timing race.
swipeHandler.onEnd(false);
result.textContent = 'PASS: cancellation safely ignored after returning to /page1';
} catch (error) {
result.textContent = String(error);
console.error(error);
}
};
{
"name": "ionic-vue-late-swipe-cancel-repro",
"private": true,
"type": "module",
"scripts": { "dev": "vite" },
"dependencies": {
"@ionic/vue": "9.0.6",
"@ionic/vue-router": "9.0.6",
"vue": "3.5.42",
"vue-router": "5.3.1",
"vite": "8.3.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment