diff --git a/packages/react/src/framework-delegate.tsx b/packages/react/src/framework-delegate.tsx index dcb4823ef00..7379b4b97e0 100644 --- a/packages/react/src/framework-delegate.tsx +++ b/packages/react/src/framework-delegate.tsx @@ -1,6 +1,8 @@ import type { FrameworkDelegate } from '@ionic/core/components'; import { createPortal } from 'react-dom'; +import { generateId } from './utils/generateId'; + // TODO(FW-2959): types type ReactComponent = (props?: any) => JSX.Element; @@ -10,6 +12,9 @@ export const ReactDelegate = ( removeView: (view: React.ReactElement) => void ): FrameworkDelegate => { const refMap = new WeakMap(); + const reactDelegateId = `react-delegate-${generateId()}`; + // Incrementing counter to generate unique keys for each view + let id = 0; const attachViewToDom = async ( parentElement: HTMLElement, @@ -22,7 +27,8 @@ export const ReactDelegate = ( parentElement.appendChild(div); const componentWithProps = component(propsOrDataObj); - const hostComponent = createPortal(componentWithProps, div); + const key = `${reactDelegateId}-${id++}`; + const hostComponent = createPortal(componentWithProps, div, key); refMap.set(div, hostComponent); diff --git a/packages/react/test/base/src/pages/navigation/NavComponent.tsx b/packages/react/test/base/src/pages/navigation/NavComponent.tsx index 96e4b14af0a..17275465af1 100644 --- a/packages/react/test/base/src/pages/navigation/NavComponent.tsx +++ b/packages/react/test/base/src/pages/navigation/NavComponent.tsx @@ -11,7 +11,7 @@ import { IonBackButton, IonPage, } from '@ionic/react'; -import React, { useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; const PageOne = ({ nav, @@ -39,7 +39,10 @@ const PageOne = ({ Go to Page Two @@ -48,7 +51,7 @@ const PageOne = ({ ); }; -const PageTwo = (props?: { someValue: string }) => { +const PageTwo = ({ nav, ...rest }: { someValue: string; nav: React.MutableRefObject }) => { return ( <> @@ -61,8 +64,8 @@ const PageTwo = (props?: { someValue: string }) => { Page two content -
{JSON.stringify(props)}
- +
{JSON.stringify(rest)}
+ }> Go to Page Three
@@ -70,7 +73,12 @@ const PageTwo = (props?: { someValue: string }) => { ); }; -const PageThree = () => { +const PageThree = ({ nav }: { nav: React.MutableRefObject }) => { + useEffect(() => { + return () => { + window.dispatchEvent(new CustomEvent('pageThreeUnmounted')); + }; + }); return ( <> @@ -81,8 +89,9 @@ const PageThree = () => { - + Page three content + nav.current.popToRoot()}>popToRoot ); diff --git a/packages/react/test/base/tests/e2e/specs/navigation/IonNav.cy.ts b/packages/react/test/base/tests/e2e/specs/navigation/IonNav.cy.ts index e0940bd3b95..3298abf0b89 100644 --- a/packages/react/test/base/tests/e2e/specs/navigation/IonNav.cy.ts +++ b/packages/react/test/base/tests/e2e/specs/navigation/IonNav.cy.ts @@ -47,4 +47,23 @@ describe('IonNav', () => { cy.get('#pageTwoProps').should('have.text', '{"someValue":"Hello"}'); }); + it('should unmount pages when popping to root', () => { + // Issue: https://github.com/ionic-team/ionic-framework/issues/27798 + + cy.contains('Go to Page Two').click(); + cy.get('#pageTwoContent').should('be.visible'); + + cy.contains('Go to Page Three').click(); + cy.get('#pageThreeContent').should('be.visible'); + + cy.window().then((window) => { + window.addEventListener('pageThreeUnmounted', cy.stub().as('pageThreeUnmounted')); + }); + + cy.get('ion-button').contains('popToRoot').click(); + cy.get('#pageThreeContent').should('not.exist'); + + cy.get('@pageThreeUnmounted').should('have.been.calledOnce'); + }); + });