Skip to content

Commit 61e4ffe

Browse files
authored
fix(react): IonNav apply properties to page components (#25603)
Resolves #25602
1 parent ea83073 commit 61e4ffe

File tree

4 files changed

+127
-74
lines changed

4 files changed

+127
-74
lines changed

packages/react/src/components/navigation/IonNav.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,34 @@ import React, { useState } from 'react';
44

55
import { ReactDelegate } from '../../framework-delegate';
66
import { createReactComponent } from '../react-component-lib';
7+
import { createForwardRef } from '../utils';
78

89
const IonNavInner = createReactComponent<
910
JSX.IonNav & { delegate: FrameworkDelegate },
1011
HTMLIonNavElement
1112
>('ion-nav', undefined, undefined, defineCustomElement);
1213

13-
export const IonNav: React.FC<JSX.IonNav> = ({ children, ...restOfProps }) => {
14-
const [views, setViews] = useState<React.ReactPortal[]>([]);
14+
type IonNavProps = JSX.IonNav & {
15+
forwardedRef?: React.ForwardedRef<HTMLIonNavElement>;
16+
};
17+
18+
const IonNavInternal: React.FC<IonNavProps> = ({ children, forwardedRef, ...restOfProps }) => {
19+
const [views, setViews] = useState<React.ReactElement[]>([]);
1520

1621
/**
1722
* Allows us to create React components that are rendered within
1823
* the context of the IonNav component.
1924
*/
20-
const addView = (view: React.ReactPortal) => setViews([...views, view]);
21-
const removeView = (view: React.ReactPortal) => setViews(views.filter((v) => v !== view));
25+
const addView = (view: React.ReactElement) => setViews([...views, view]);
26+
const removeView = (view: React.ReactElement) => setViews(views.filter((v) => v !== view));
2227

2328
const delegate = ReactDelegate(addView, removeView);
2429

2530
return (
26-
<IonNavInner delegate={delegate} {...restOfProps}>
31+
<IonNavInner delegate={delegate} ref={forwardedRef} {...restOfProps}>
2732
{views}
2833
</IonNavInner>
2934
);
3035
};
36+
37+
export const IonNav = createForwardRef<IonNavProps, HTMLIonNavElement>(IonNavInternal, 'IonNav');

packages/react/src/framework-delegate.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
11
import { FrameworkDelegate } from '@ionic/core/components';
22
import { createPortal } from 'react-dom';
33

4+
type ReactComponent = (props?: any) => JSX.Element;
5+
46
export const ReactDelegate = (
5-
addView: (view: React.ReactPortal) => void,
6-
removeView: (view: React.ReactPortal) => void
7+
addView: (view: React.ReactElement) => void,
8+
removeView: (view: React.ReactElement) => void
79
): FrameworkDelegate => {
8-
let Component: React.ReactPortal;
10+
const refMap = new WeakMap<ReactComponent, React.ReactElement>();
911

1012
const attachViewToDom = async (
1113
parentElement: HTMLElement,
12-
component: () => JSX.Element,
14+
component: ReactComponent,
1315
propsOrDataObj?: any,
1416
cssClasses?: string[]
1517
): Promise<any> => {
1618
const div = document.createElement('div');
1719
cssClasses && div.classList.add(...cssClasses);
1820
parentElement.appendChild(div);
1921

20-
Component = createPortal(component(), div);
22+
const componentWithProps = component(propsOrDataObj);
23+
const hostComponent = createPortal(componentWithProps, div);
2124

22-
Component.props = propsOrDataObj;
25+
refMap.set(component, hostComponent);
2326

24-
addView(Component);
27+
addView(hostComponent);
2528

2629
return Promise.resolve(div);
2730
};
2831

29-
const removeViewFromDom = (): Promise<void> => {
30-
Component && removeView(Component);
32+
const removeViewFromDom = (_container: any, component: ReactComponent): Promise<void> => {
33+
const hostComponent = refMap.get(component);
34+
hostComponent && removeView(hostComponent);
35+
3136
return Promise.resolve();
3237
};
3338

packages/react/test-app/cypress/integration/navigation/IonNav.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ describe('IonNav', () => {
77
cy.get('ion-nav').contains('Page one content');
88
});
99

10+
it('should have a ref defined', () => {
11+
cy.get('#navRef').should('have.text', 'Nav ref is defined: true');
12+
});
13+
1014
it('should push a page', () => {
1115
cy.get('ion-button').contains('Go to Page Two').click();
1216
cy.get('#pageTwoContent').should('be.visible');
@@ -25,4 +29,16 @@ describe('IonNav', () => {
2529
cy.get('ion-nav').contains('Page one content');
2630
});
2731

32+
it('should pass params to the page', () => {
33+
cy.get('#pageOneProps').should('have.text', '{"someString":"Hello","someNumber":3,"someBoolean":true}');
34+
});
35+
36+
it('should pass componentProps to sub pages', () => {
37+
cy.get('ion-button').contains('Go to Page Two').click();
38+
39+
cy.get('#pageTwoContent').should('be.visible');
40+
41+
cy.get('#pageTwoProps').should('have.text', '{"someValue":"Hello"}');
42+
});
43+
2844
});

packages/react/test-app/src/pages/navigation/NavComponent.tsx

Lines changed: 85 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -11,72 +11,97 @@ import {
1111
IonBackButton,
1212
IonPage,
1313
} from '@ionic/react';
14-
import React from 'react';
14+
import React, { useRef } from 'react';
15+
16+
const PageOne = ({
17+
nav,
18+
...restOfProps
19+
}: {
20+
someString: string;
21+
someNumber: number;
22+
someBoolean: boolean;
23+
nav: React.MutableRefObject<HTMLIonNavElement>;
24+
}) => {
25+
return (
26+
<>
27+
<IonHeader>
28+
<IonToolbar>
29+
<IonTitle>Page One</IonTitle>
30+
<IonButtons>
31+
<IonBackButton />
32+
</IonButtons>
33+
</IonToolbar>
34+
</IonHeader>
35+
<IonContent id="pageOneContent">
36+
<IonLabel>Page one content</IonLabel>
37+
<div id="pageOneProps">{JSON.stringify(restOfProps)}</div>
38+
<div id="navRef">Nav ref is defined: {nav.current !== null ? 'true' : 'false'}</div>
39+
<IonNavLink
40+
routerDirection="forward"
41+
component={PageTwo}
42+
componentProps={{ someValue: 'Hello' }}
43+
>
44+
<IonButton>Go to Page Two</IonButton>
45+
</IonNavLink>
46+
</IonContent>
47+
</>
48+
);
49+
};
50+
51+
const PageTwo = (props?: { someValue: string }) => {
52+
return (
53+
<>
54+
<IonHeader>
55+
<IonToolbar>
56+
<IonTitle>Page Two</IonTitle>
57+
<IonButtons>
58+
<IonBackButton />
59+
</IonButtons>
60+
</IonToolbar>
61+
</IonHeader>
62+
<IonContent id="pageTwoContent">
63+
<IonLabel>Page two content</IonLabel>
64+
<div id="pageTwoProps">{JSON.stringify(props)}</div>
65+
<IonNavLink routerDirection="forward" component={PageThree}>
66+
<IonButton>Go to Page Three</IonButton>
67+
</IonNavLink>
68+
</IonContent>
69+
</>
70+
);
71+
};
72+
73+
const PageThree = () => {
74+
return (
75+
<>
76+
<IonHeader>
77+
<IonToolbar>
78+
<IonTitle>Page Three</IonTitle>
79+
<IonButtons>
80+
<IonBackButton />
81+
</IonButtons>
82+
</IonToolbar>
83+
</IonHeader>
84+
<IonContent>
85+
<IonLabel>Page three content</IonLabel>
86+
</IonContent>
87+
</>
88+
);
89+
};
1590

1691
const NavComponent: React.FC = () => {
92+
const ref = useRef<any>();
1793
return (
1894
<IonPage>
1995
<IonNav
20-
root={() => {
21-
return (
22-
<>
23-
<IonHeader>
24-
<IonToolbar>
25-
<IonTitle>Page One</IonTitle>
26-
<IonButtons>
27-
<IonBackButton />
28-
</IonButtons>
29-
</IonToolbar>
30-
</IonHeader>
31-
<IonContent id="pageOneContent">
32-
<IonLabel>Page one content</IonLabel>
33-
<IonNavLink
34-
routerDirection="forward"
35-
component={() => {
36-
return (
37-
<>
38-
<IonHeader>
39-
<IonToolbar>
40-
<IonTitle>Page Two</IonTitle>
41-
<IonButtons>
42-
<IonBackButton />
43-
</IonButtons>
44-
</IonToolbar>
45-
</IonHeader>
46-
<IonContent id="pageTwoContent">
47-
<IonLabel>Page two content</IonLabel>
48-
<IonNavLink
49-
routerDirection="forward"
50-
component={() => (
51-
<>
52-
<IonHeader>
53-
<IonToolbar>
54-
<IonTitle>Page Three</IonTitle>
55-
<IonButtons>
56-
<IonBackButton />
57-
</IonButtons>
58-
</IonToolbar>
59-
</IonHeader>
60-
<IonContent>
61-
<IonLabel>Page three content</IonLabel>
62-
</IonContent>
63-
</>
64-
)}
65-
>
66-
<IonButton>Go to Page Three</IonButton>
67-
</IonNavLink>
68-
</IonContent>
69-
</>
70-
);
71-
}}
72-
>
73-
<IonButton>Go to Page Two</IonButton>
74-
</IonNavLink>
75-
</IonContent>
76-
</>
77-
);
96+
ref={ref}
97+
root={PageOne}
98+
rootParams={{
99+
someString: 'Hello',
100+
someNumber: 3,
101+
someBoolean: true,
102+
nav: ref,
78103
}}
79-
></IonNav>
104+
/>
80105
</IonPage>
81106
);
82107
};

0 commit comments

Comments
 (0)