Skip to content

Commit 0edcb2c

Browse files
zhbhunsean-perkins
andauthored
fix(react): Nav unmounts component while invoking popTo or popToRoot (#27821)
Issue number: Resolves #27798 --------- ## What is the current behavior React IonNav component's views are missing keys, leading to unnecessary duplicate mounting of components. ## What is the new behavior? - Adds key to views of React IonNav component. ## Does this introduce a breaking change? - [ ] Yes - [x] No --------- Co-authored-by: Sean Perkins <[email protected]>
1 parent 7b197a3 commit 0edcb2c

File tree

3 files changed

+42
-8
lines changed

3 files changed

+42
-8
lines changed

packages/react/src/framework-delegate.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { FrameworkDelegate } from '@ionic/core/components';
22
import { createPortal } from 'react-dom';
33

4+
import { generateId } from './utils/generateId';
5+
46
// TODO(FW-2959): types
57

68
type ReactComponent = (props?: any) => JSX.Element;
@@ -10,6 +12,9 @@ export const ReactDelegate = (
1012
removeView: (view: React.ReactElement) => void
1113
): FrameworkDelegate => {
1214
const refMap = new WeakMap<HTMLElement, React.ReactElement>();
15+
const reactDelegateId = `react-delegate-${generateId()}`;
16+
// Incrementing counter to generate unique keys for each view
17+
let id = 0;
1318

1419
const attachViewToDom = async (
1520
parentElement: HTMLElement,
@@ -22,7 +27,8 @@ export const ReactDelegate = (
2227
parentElement.appendChild(div);
2328

2429
const componentWithProps = component(propsOrDataObj);
25-
const hostComponent = createPortal(componentWithProps, div);
30+
const key = `${reactDelegateId}-${id++}`;
31+
const hostComponent = createPortal(componentWithProps, div, key);
2632

2733
refMap.set(div, hostComponent);
2834

packages/react/test/base/src/pages/navigation/NavComponent.tsx

+16-7
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
IonBackButton,
1212
IonPage,
1313
} from '@ionic/react';
14-
import React, { useRef } from 'react';
14+
import React, { useEffect, useRef } from 'react';
1515

1616
const PageOne = ({
1717
nav,
@@ -39,7 +39,10 @@ const PageOne = ({
3939
<IonNavLink
4040
routerDirection="forward"
4141
component={PageTwo}
42-
componentProps={{ someValue: 'Hello' }}
42+
componentProps={{
43+
someValue: 'Hello',
44+
nav: nav,
45+
}}
4346
>
4447
<IonButton>Go to Page Two</IonButton>
4548
</IonNavLink>
@@ -48,7 +51,7 @@ const PageOne = ({
4851
);
4952
};
5053

51-
const PageTwo = (props?: { someValue: string }) => {
54+
const PageTwo = ({ nav, ...rest }: { someValue: string; nav: React.MutableRefObject<HTMLIonNavElement> }) => {
5255
return (
5356
<>
5457
<IonHeader>
@@ -61,16 +64,21 @@ const PageTwo = (props?: { someValue: string }) => {
6164
</IonHeader>
6265
<IonContent id="pageTwoContent">
6366
<IonLabel>Page two content</IonLabel>
64-
<div id="pageTwoProps">{JSON.stringify(props)}</div>
65-
<IonNavLink routerDirection="forward" component={PageThree}>
67+
<div id="pageTwoProps">{JSON.stringify(rest)}</div>
68+
<IonNavLink routerDirection="forward" component={() => <PageThree nav={nav} />}>
6669
<IonButton>Go to Page Three</IonButton>
6770
</IonNavLink>
6871
</IonContent>
6972
</>
7073
);
7174
};
7275

73-
const PageThree = () => {
76+
const PageThree = ({ nav }: { nav: React.MutableRefObject<HTMLIonNavElement> }) => {
77+
useEffect(() => {
78+
return () => {
79+
window.dispatchEvent(new CustomEvent('pageThreeUnmounted'));
80+
};
81+
});
7482
return (
7583
<>
7684
<IonHeader>
@@ -81,8 +89,9 @@ const PageThree = () => {
8189
</IonButtons>
8290
</IonToolbar>
8391
</IonHeader>
84-
<IonContent>
92+
<IonContent id="pageThreeContent">
8593
<IonLabel>Page three content</IonLabel>
94+
<IonButton onClick={() => nav.current.popToRoot()}>popToRoot</IonButton>
8695
</IonContent>
8796
</>
8897
);

packages/react/test/base/tests/e2e/specs/navigation/IonNav.cy.ts

+19
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,23 @@ describe('IonNav', () => {
4747
cy.get('#pageTwoProps').should('have.text', '{"someValue":"Hello"}');
4848
});
4949

50+
it('should unmount pages when popping to root', () => {
51+
// Issue: https://github.com/ionic-team/ionic-framework/issues/27798
52+
53+
cy.contains('Go to Page Two').click();
54+
cy.get('#pageTwoContent').should('be.visible');
55+
56+
cy.contains('Go to Page Three').click();
57+
cy.get('#pageThreeContent').should('be.visible');
58+
59+
cy.window().then((window) => {
60+
window.addEventListener('pageThreeUnmounted', cy.stub().as('pageThreeUnmounted'));
61+
});
62+
63+
cy.get('ion-button').contains('popToRoot').click();
64+
cy.get('#pageThreeContent').should('not.exist');
65+
66+
cy.get('@pageThreeUnmounted').should('have.been.calledOnce');
67+
});
68+
5069
});

0 commit comments

Comments
 (0)