mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
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 <sean@ionic.io>
This commit is contained in:
@ -1,6 +1,8 @@
|
|||||||
import type { FrameworkDelegate } from '@ionic/core/components';
|
import type { FrameworkDelegate } from '@ionic/core/components';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
import { generateId } from './utils/generateId';
|
||||||
|
|
||||||
// TODO(FW-2959): types
|
// TODO(FW-2959): types
|
||||||
|
|
||||||
type ReactComponent = (props?: any) => JSX.Element;
|
type ReactComponent = (props?: any) => JSX.Element;
|
||||||
@ -10,6 +12,9 @@ export const ReactDelegate = (
|
|||||||
removeView: (view: React.ReactElement) => void
|
removeView: (view: React.ReactElement) => void
|
||||||
): FrameworkDelegate => {
|
): FrameworkDelegate => {
|
||||||
const refMap = new WeakMap<HTMLElement, React.ReactElement>();
|
const refMap = new WeakMap<HTMLElement, React.ReactElement>();
|
||||||
|
const reactDelegateId = `react-delegate-${generateId()}`;
|
||||||
|
// Incrementing counter to generate unique keys for each view
|
||||||
|
let id = 0;
|
||||||
|
|
||||||
const attachViewToDom = async (
|
const attachViewToDom = async (
|
||||||
parentElement: HTMLElement,
|
parentElement: HTMLElement,
|
||||||
@ -22,7 +27,8 @@ export const ReactDelegate = (
|
|||||||
parentElement.appendChild(div);
|
parentElement.appendChild(div);
|
||||||
|
|
||||||
const componentWithProps = component(propsOrDataObj);
|
const componentWithProps = component(propsOrDataObj);
|
||||||
const hostComponent = createPortal(componentWithProps, div);
|
const key = `${reactDelegateId}-${id++}`;
|
||||||
|
const hostComponent = createPortal(componentWithProps, div, key);
|
||||||
|
|
||||||
refMap.set(div, hostComponent);
|
refMap.set(div, hostComponent);
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
IonBackButton,
|
IonBackButton,
|
||||||
IonPage,
|
IonPage,
|
||||||
} from '@ionic/react';
|
} from '@ionic/react';
|
||||||
import React, { useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
const PageOne = ({
|
const PageOne = ({
|
||||||
nav,
|
nav,
|
||||||
@ -39,7 +39,10 @@ const PageOne = ({
|
|||||||
<IonNavLink
|
<IonNavLink
|
||||||
routerDirection="forward"
|
routerDirection="forward"
|
||||||
component={PageTwo}
|
component={PageTwo}
|
||||||
componentProps={{ someValue: 'Hello' }}
|
componentProps={{
|
||||||
|
someValue: 'Hello',
|
||||||
|
nav: nav,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<IonButton>Go to Page Two</IonButton>
|
<IonButton>Go to Page Two</IonButton>
|
||||||
</IonNavLink>
|
</IonNavLink>
|
||||||
@ -48,7 +51,7 @@ const PageOne = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PageTwo = (props?: { someValue: string }) => {
|
const PageTwo = ({ nav, ...rest }: { someValue: string; nav: React.MutableRefObject<HTMLIonNavElement> }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IonHeader>
|
<IonHeader>
|
||||||
@ -61,8 +64,8 @@ const PageTwo = (props?: { someValue: string }) => {
|
|||||||
</IonHeader>
|
</IonHeader>
|
||||||
<IonContent id="pageTwoContent">
|
<IonContent id="pageTwoContent">
|
||||||
<IonLabel>Page two content</IonLabel>
|
<IonLabel>Page two content</IonLabel>
|
||||||
<div id="pageTwoProps">{JSON.stringify(props)}</div>
|
<div id="pageTwoProps">{JSON.stringify(rest)}</div>
|
||||||
<IonNavLink routerDirection="forward" component={PageThree}>
|
<IonNavLink routerDirection="forward" component={() => <PageThree nav={nav} />}>
|
||||||
<IonButton>Go to Page Three</IonButton>
|
<IonButton>Go to Page Three</IonButton>
|
||||||
</IonNavLink>
|
</IonNavLink>
|
||||||
</IonContent>
|
</IonContent>
|
||||||
@ -70,7 +73,12 @@ const PageTwo = (props?: { someValue: string }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PageThree = () => {
|
const PageThree = ({ nav }: { nav: React.MutableRefObject<HTMLIonNavElement> }) => {
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
window.dispatchEvent(new CustomEvent('pageThreeUnmounted'));
|
||||||
|
};
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IonHeader>
|
<IonHeader>
|
||||||
@ -81,8 +89,9 @@ const PageThree = () => {
|
|||||||
</IonButtons>
|
</IonButtons>
|
||||||
</IonToolbar>
|
</IonToolbar>
|
||||||
</IonHeader>
|
</IonHeader>
|
||||||
<IonContent>
|
<IonContent id="pageThreeContent">
|
||||||
<IonLabel>Page three content</IonLabel>
|
<IonLabel>Page three content</IonLabel>
|
||||||
|
<IonButton onClick={() => nav.current.popToRoot()}>popToRoot</IonButton>
|
||||||
</IonContent>
|
</IonContent>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -47,4 +47,23 @@ describe('IonNav', () => {
|
|||||||
cy.get('#pageTwoProps').should('have.text', '{"someValue":"Hello"}');
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user