mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 18:54:11 +08:00
fix(react): IonNav apply properties to page components (#25603)
Resolves #25602
This commit is contained in:
@ -4,27 +4,34 @@ import React, { useState } from 'react';
|
|||||||
|
|
||||||
import { ReactDelegate } from '../../framework-delegate';
|
import { ReactDelegate } from '../../framework-delegate';
|
||||||
import { createReactComponent } from '../react-component-lib';
|
import { createReactComponent } from '../react-component-lib';
|
||||||
|
import { createForwardRef } from '../utils';
|
||||||
|
|
||||||
const IonNavInner = createReactComponent<
|
const IonNavInner = createReactComponent<
|
||||||
JSX.IonNav & { delegate: FrameworkDelegate },
|
JSX.IonNav & { delegate: FrameworkDelegate },
|
||||||
HTMLIonNavElement
|
HTMLIonNavElement
|
||||||
>('ion-nav', undefined, undefined, defineCustomElement);
|
>('ion-nav', undefined, undefined, defineCustomElement);
|
||||||
|
|
||||||
export const IonNav: React.FC<JSX.IonNav> = ({ children, ...restOfProps }) => {
|
type IonNavProps = JSX.IonNav & {
|
||||||
const [views, setViews] = useState<React.ReactPortal[]>([]);
|
forwardedRef?: React.ForwardedRef<HTMLIonNavElement>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const IonNavInternal: React.FC<IonNavProps> = ({ children, forwardedRef, ...restOfProps }) => {
|
||||||
|
const [views, setViews] = useState<React.ReactElement[]>([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows us to create React components that are rendered within
|
* Allows us to create React components that are rendered within
|
||||||
* the context of the IonNav component.
|
* the context of the IonNav component.
|
||||||
*/
|
*/
|
||||||
const addView = (view: React.ReactPortal) => setViews([...views, view]);
|
const addView = (view: React.ReactElement) => setViews([...views, view]);
|
||||||
const removeView = (view: React.ReactPortal) => setViews(views.filter((v) => v !== view));
|
const removeView = (view: React.ReactElement) => setViews(views.filter((v) => v !== view));
|
||||||
|
|
||||||
const delegate = ReactDelegate(addView, removeView);
|
const delegate = ReactDelegate(addView, removeView);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IonNavInner delegate={delegate} {...restOfProps}>
|
<IonNavInner delegate={delegate} ref={forwardedRef} {...restOfProps}>
|
||||||
{views}
|
{views}
|
||||||
</IonNavInner>
|
</IonNavInner>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const IonNav = createForwardRef<IonNavProps, HTMLIonNavElement>(IonNavInternal, 'IonNav');
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import { FrameworkDelegate } from '@ionic/core/components';
|
import { FrameworkDelegate } from '@ionic/core/components';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
type ReactComponent = (props?: any) => JSX.Element;
|
||||||
|
|
||||||
export const ReactDelegate = (
|
export const ReactDelegate = (
|
||||||
addView: (view: React.ReactPortal) => void,
|
addView: (view: React.ReactElement) => void,
|
||||||
removeView: (view: React.ReactPortal) => void
|
removeView: (view: React.ReactElement) => void
|
||||||
): FrameworkDelegate => {
|
): FrameworkDelegate => {
|
||||||
let Component: React.ReactPortal;
|
const refMap = new WeakMap<ReactComponent, React.ReactElement>();
|
||||||
|
|
||||||
const attachViewToDom = async (
|
const attachViewToDom = async (
|
||||||
parentElement: HTMLElement,
|
parentElement: HTMLElement,
|
||||||
component: () => JSX.Element,
|
component: ReactComponent,
|
||||||
propsOrDataObj?: any,
|
propsOrDataObj?: any,
|
||||||
cssClasses?: string[]
|
cssClasses?: string[]
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
@ -17,17 +19,20 @@ export const ReactDelegate = (
|
|||||||
cssClasses && div.classList.add(...cssClasses);
|
cssClasses && div.classList.add(...cssClasses);
|
||||||
parentElement.appendChild(div);
|
parentElement.appendChild(div);
|
||||||
|
|
||||||
Component = createPortal(component(), div);
|
const componentWithProps = component(propsOrDataObj);
|
||||||
|
const hostComponent = createPortal(componentWithProps, div);
|
||||||
|
|
||||||
Component.props = propsOrDataObj;
|
refMap.set(component, hostComponent);
|
||||||
|
|
||||||
addView(Component);
|
addView(hostComponent);
|
||||||
|
|
||||||
return Promise.resolve(div);
|
return Promise.resolve(div);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeViewFromDom = (): Promise<void> => {
|
const removeViewFromDom = (_container: any, component: ReactComponent): Promise<void> => {
|
||||||
Component && removeView(Component);
|
const hostComponent = refMap.get(component);
|
||||||
|
hostComponent && removeView(hostComponent);
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,6 +7,10 @@ describe('IonNav', () => {
|
|||||||
cy.get('ion-nav').contains('Page one content');
|
cy.get('ion-nav').contains('Page one content');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have a ref defined', () => {
|
||||||
|
cy.get('#navRef').should('have.text', 'Nav ref is defined: true');
|
||||||
|
});
|
||||||
|
|
||||||
it('should push a page', () => {
|
it('should push a page', () => {
|
||||||
cy.get('ion-button').contains('Go to Page Two').click();
|
cy.get('ion-button').contains('Go to Page Two').click();
|
||||||
cy.get('#pageTwoContent').should('be.visible');
|
cy.get('#pageTwoContent').should('be.visible');
|
||||||
@ -25,4 +29,16 @@ describe('IonNav', () => {
|
|||||||
cy.get('ion-nav').contains('Page one content');
|
cy.get('ion-nav').contains('Page one content');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should pass params to the page', () => {
|
||||||
|
cy.get('#pageOneProps').should('have.text', '{"someString":"Hello","someNumber":3,"someBoolean":true}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass componentProps to sub pages', () => {
|
||||||
|
cy.get('ion-button').contains('Go to Page Two').click();
|
||||||
|
|
||||||
|
cy.get('#pageTwoContent').should('be.visible');
|
||||||
|
|
||||||
|
cy.get('#pageTwoProps').should('have.text', '{"someValue":"Hello"}');
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -11,72 +11,97 @@ import {
|
|||||||
IonBackButton,
|
IonBackButton,
|
||||||
IonPage,
|
IonPage,
|
||||||
} from '@ionic/react';
|
} from '@ionic/react';
|
||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
|
||||||
|
const PageOne = ({
|
||||||
|
nav,
|
||||||
|
...restOfProps
|
||||||
|
}: {
|
||||||
|
someString: string;
|
||||||
|
someNumber: number;
|
||||||
|
someBoolean: boolean;
|
||||||
|
nav: React.MutableRefObject<HTMLIonNavElement>;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Page One</IonTitle>
|
||||||
|
<IonButtons>
|
||||||
|
<IonBackButton />
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
<IonContent id="pageOneContent">
|
||||||
|
<IonLabel>Page one content</IonLabel>
|
||||||
|
<div id="pageOneProps">{JSON.stringify(restOfProps)}</div>
|
||||||
|
<div id="navRef">Nav ref is defined: {nav.current !== null ? 'true' : 'false'}</div>
|
||||||
|
<IonNavLink
|
||||||
|
routerDirection="forward"
|
||||||
|
component={PageTwo}
|
||||||
|
componentProps={{ someValue: 'Hello' }}
|
||||||
|
>
|
||||||
|
<IonButton>Go to Page Two</IonButton>
|
||||||
|
</IonNavLink>
|
||||||
|
</IonContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PageTwo = (props?: { someValue: string }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Page Two</IonTitle>
|
||||||
|
<IonButtons>
|
||||||
|
<IonBackButton />
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
<IonContent id="pageTwoContent">
|
||||||
|
<IonLabel>Page two content</IonLabel>
|
||||||
|
<div id="pageTwoProps">{JSON.stringify(props)}</div>
|
||||||
|
<IonNavLink routerDirection="forward" component={PageThree}>
|
||||||
|
<IonButton>Go to Page Three</IonButton>
|
||||||
|
</IonNavLink>
|
||||||
|
</IonContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PageThree = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Page Three</IonTitle>
|
||||||
|
<IonButtons>
|
||||||
|
<IonBackButton />
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
<IonContent>
|
||||||
|
<IonLabel>Page three content</IonLabel>
|
||||||
|
</IonContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const NavComponent: React.FC = () => {
|
const NavComponent: React.FC = () => {
|
||||||
|
const ref = useRef<any>();
|
||||||
return (
|
return (
|
||||||
<IonPage>
|
<IonPage>
|
||||||
<IonNav
|
<IonNav
|
||||||
root={() => {
|
ref={ref}
|
||||||
return (
|
root={PageOne}
|
||||||
<>
|
rootParams={{
|
||||||
<IonHeader>
|
someString: 'Hello',
|
||||||
<IonToolbar>
|
someNumber: 3,
|
||||||
<IonTitle>Page One</IonTitle>
|
someBoolean: true,
|
||||||
<IonButtons>
|
nav: ref,
|
||||||
<IonBackButton />
|
|
||||||
</IonButtons>
|
|
||||||
</IonToolbar>
|
|
||||||
</IonHeader>
|
|
||||||
<IonContent id="pageOneContent">
|
|
||||||
<IonLabel>Page one content</IonLabel>
|
|
||||||
<IonNavLink
|
|
||||||
routerDirection="forward"
|
|
||||||
component={() => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<IonHeader>
|
|
||||||
<IonToolbar>
|
|
||||||
<IonTitle>Page Two</IonTitle>
|
|
||||||
<IonButtons>
|
|
||||||
<IonBackButton />
|
|
||||||
</IonButtons>
|
|
||||||
</IonToolbar>
|
|
||||||
</IonHeader>
|
|
||||||
<IonContent id="pageTwoContent">
|
|
||||||
<IonLabel>Page two content</IonLabel>
|
|
||||||
<IonNavLink
|
|
||||||
routerDirection="forward"
|
|
||||||
component={() => (
|
|
||||||
<>
|
|
||||||
<IonHeader>
|
|
||||||
<IonToolbar>
|
|
||||||
<IonTitle>Page Three</IonTitle>
|
|
||||||
<IonButtons>
|
|
||||||
<IonBackButton />
|
|
||||||
</IonButtons>
|
|
||||||
</IonToolbar>
|
|
||||||
</IonHeader>
|
|
||||||
<IonContent>
|
|
||||||
<IonLabel>Page three content</IonLabel>
|
|
||||||
</IonContent>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<IonButton>Go to Page Three</IonButton>
|
|
||||||
</IonNavLink>
|
|
||||||
</IonContent>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IonButton>Go to Page Two</IonButton>
|
|
||||||
</IonNavLink>
|
|
||||||
</IonContent>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
></IonNav>
|
/>
|
||||||
</IonPage>
|
</IonPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user