Skip to content

bug: React IonRouterOutlet throws when unmounted before initialization completes #31513

Description

@ptmkenny

Prerequisites

Ionic Framework Version

v9.x

Current Behavior

While optimizing page loading in my Ionic React app, I started seeing intermittent uncaught exceptions during registration and onboarding in Playwright.

Astra analysis:

Unmounting an IonRouterOutlet with ionPage before its deferred initialization callback runs causes an uncaught exception:

TypeError: Cannot read properties of null (reading 'classList')
    at http://localhost:5173/node_modules/.vite/deps/chunk-K6ONCLJS.js?v=7fa17f37:13510:19
    at http://localhost:5173/node_modules/.vite/deps/chunk-ANOI66QA.js?v=7fa17f37:35:75

The first frame is the componentOnReady callback in OutletPageManager.componentDidMount().

The reproduction mounts the outlet, then removes it in the parent component's layout effect. This makes the problematic lifecycle ordering deterministic: the child's componentDidMount schedules initialization before the parent's layout effect removes it. No framework internals are mocked or patched, and no timers or network throttling are needed.

Expected Behavior

If the outlet is unmounted before initialization completes, the pending callback should exit without accessing the cleared ref, changing page classes, or registering a stale outlet. No uncaught error should occur.

Steps to Reproduce

  1. Clone the reproduction and install dependencies:

    git clone --branch outletPageManager https://github.com/ptmkenny/ionic-react-router-6-test.git
    cd ionic-react-router-6-test
    npm install
    npm run dev
  2. Open the URL printed by Vite, normally http://localhost:5173/.

  3. Click Mount and unmount outlet.

  4. Observe Attempts: 1, Uncaught errors: 1, and the classList TypeError printed on the page and in the console.

  5. Click again. Each click mounts and removes a new outlet and adds another error. Expected: the error counter remains zero.

Code Reproduction URL

https://github.com/ptmkenny/ionic-react-router-6-test/tree/outletPageManager

Ionic Info

Ionic:

   Ionic CLI       : 7.2.1 (/home/node/.npm/_npx/f6fddb685269761d/node_modules/@ionic/cli)
   Ionic Framework : @ionic/react 9.0.6

Capacitor:

   Capacitor CLI      : 8.0.0
   @capacitor/android : not installed
   @capacitor/core    : 8.0.0
   @capacitor/ios     : not installed

Utility:

   cordova-res : not installed globally
   native-run  : 2.0.1

System:

   NodeJS : v26.10.0 (/usr/local/bin/node)
   npm    : 11.19.1
   OS     : Linux 6.18

Additional Information

From Astra:

Root cause

In OutletPageManager.tsx, componentDidMount() checks this.ionRouterOutlet before scheduling componentOnReady. The callback later reads this.ionRouterOutlet! again and immediately accesses el.classList. React can clear the ref between scheduling and execution. The non-null assertion does not prevent runtime null access.

The same unsafe callback exists in upstream main at 643584b3ac (2026-10-01). A check made only before scheduling is insufficient. The callback must also belong to the current mount and outlet when it executes.

Activity

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

Metadata

Metadata

Assignees

Labels

type: buga confirmed bug report

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions