Skip to content

Commit f4941f2

Browse files
feat(react): fixing support for react 19, adding test app for react 19 (#30217)
Issue number: resolves #29991 Co-authored-by: Brandy Smith <[email protected]>
1 parent 521d077 commit f4941f2

20 files changed

+11062
-293
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ jobs:
198198
strategy:
199199
fail-fast: false
200200
matrix:
201-
apps: [react17, react18]
201+
apps: [react17, react18, react19]
202202
needs: [build-react, build-react-router]
203203
runs-on: ubuntu-latest
204204
steps:

.github/workflows/stencil-nightly.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ jobs:
208208
strategy:
209209
fail-fast: false
210210
matrix:
211-
apps: [react17, react18]
211+
apps: [react17, react18, react19]
212212
needs: [build-react, build-react-router]
213213
runs-on: ubuntu-latest
214214
steps:
+45-49
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { JSX as LocalJSX } from '@ionic/core/components';
2-
import React from 'react';
2+
import React, { type PropsWithChildren } from 'react';
33

44
import type { IonContextInterface } from '../contexts/IonContext';
55
import { IonContext } from '../contexts/IonContext';
@@ -9,53 +9,49 @@ import { IonOverlayManager } from './IonOverlayManager';
99
import type { IonicReactProps } from './IonicReactProps';
1010
import { IonAppInner } from './inner-proxies';
1111

12-
type Props = LocalJSX.IonApp &
13-
IonicReactProps & {
14-
ref?: React.Ref<HTMLIonAppElement>;
15-
};
16-
17-
export const IonApp = /*@__PURE__*/ (() =>
18-
class extends React.Component<Props> {
19-
addOverlayCallback?: (id: string, overlay: ReactComponentOrElement, containerElement: HTMLDivElement) => void;
20-
removeOverlayCallback?: (id: string) => void;
21-
22-
constructor(props: Props) {
23-
super(props);
24-
}
25-
26-
/*
27-
Wire up methods to call into IonOverlayManager
28-
*/
29-
ionContext: IonContextInterface = {
30-
addOverlay: (id: string, overlay: ReactComponentOrElement, containerElement: HTMLDivElement) => {
31-
if (this.addOverlayCallback) {
32-
this.addOverlayCallback(id, overlay, containerElement);
33-
}
34-
},
35-
removeOverlay: (id: string) => {
36-
if (this.removeOverlayCallback) {
37-
this.removeOverlayCallback(id);
38-
}
39-
},
40-
};
41-
42-
render() {
43-
return (
44-
<IonContext.Provider value={this.ionContext}>
45-
<IonAppInner {...this.props}>{this.props.children}</IonAppInner>
46-
<IonOverlayManager
47-
onAddOverlay={(callback) => {
48-
this.addOverlayCallback = callback;
49-
}}
50-
onRemoveOverlay={(callback) => {
51-
this.removeOverlayCallback = callback;
52-
}}
53-
/>
54-
</IonContext.Provider>
55-
);
12+
type Props = PropsWithChildren<
13+
LocalJSX.IonApp &
14+
IonicReactProps & {
15+
ref?: React.Ref<HTMLIonAppElement>;
5616
}
17+
>;
18+
19+
export class IonApp extends React.Component<Props> {
20+
addOverlayCallback?: (id: string, overlay: ReactComponentOrElement, containerElement: HTMLDivElement) => void;
21+
removeOverlayCallback?: (id: string) => void;
22+
23+
constructor(props: Props) {
24+
super(props);
25+
}
26+
27+
ionContext: IonContextInterface = {
28+
addOverlay: (id: string, overlay: ReactComponentOrElement, containerElement: HTMLDivElement) => {
29+
if (this.addOverlayCallback) {
30+
this.addOverlayCallback(id, overlay, containerElement);
31+
}
32+
},
33+
removeOverlay: (id: string) => {
34+
if (this.removeOverlayCallback) {
35+
this.removeOverlayCallback(id);
36+
}
37+
},
38+
};
5739

58-
static get displayName() {
59-
return 'IonApp';
60-
}
61-
})();
40+
render() {
41+
return (
42+
<IonContext.Provider value={this.ionContext}>
43+
<IonAppInner {...this.props}>{this.props.children}</IonAppInner>
44+
<IonOverlayManager
45+
onAddOverlay={(callback) => {
46+
this.addOverlayCallback = callback;
47+
}}
48+
onRemoveOverlay={(callback) => {
49+
this.removeOverlayCallback = callback;
50+
}}
51+
/>
52+
</IonContext.Provider>
53+
);
54+
}
55+
56+
static displayName = 'IonApp';
57+
}
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,53 @@
11
import type { JSX as LocalJSX } from '@ionic/core/components';
2-
import React from 'react';
2+
import React, { type PropsWithChildren } from 'react';
33

44
import { NavContext } from '../../contexts/NavContext';
55
import type { IonicReactProps } from '../IonicReactProps';
66
import { IonBackButtonInner } from '../inner-proxies';
77

8-
type Props = Omit<LocalJSX.IonBackButton, 'icon'> &
9-
IonicReactProps & {
10-
icon?:
11-
| {
12-
ios: string;
13-
md: string;
14-
}
15-
| string;
16-
ref?: React.Ref<HTMLIonBackButtonElement>;
17-
};
18-
19-
export const IonBackButton = /*@__PURE__*/ (() =>
20-
class extends React.Component<Props> {
21-
context!: React.ContextType<typeof NavContext>;
22-
23-
clickButton = (e: React.MouseEvent) => {
24-
/**
25-
* If ion-back-button is being used inside
26-
* of ion-nav then we should not interact with
27-
* the router.
28-
*/
29-
if (e.target && (e.target as HTMLElement).closest('ion-nav') !== null) {
30-
return;
31-
}
32-
33-
const { defaultHref, routerAnimation } = this.props;
34-
35-
if (this.context.hasIonicRouter()) {
36-
e.stopPropagation();
37-
this.context.goBack(defaultHref, routerAnimation);
38-
} else if (defaultHref !== undefined) {
39-
window.location.href = defaultHref;
40-
}
41-
};
42-
43-
render() {
44-
return <IonBackButtonInner onClick={this.clickButton} {...this.props}></IonBackButtonInner>;
8+
type Props = PropsWithChildren<
9+
LocalJSX.IonBackButton &
10+
IonicReactProps & {
11+
ref?: React.Ref<HTMLIonBackButtonElement>;
4512
}
46-
47-
static get displayName() {
48-
return 'IonBackButton';
13+
>;
14+
15+
export class IonBackButton extends React.Component<Props> {
16+
context!: React.ContextType<typeof NavContext>;
17+
18+
clickButton = (e: React.MouseEvent) => {
19+
/**
20+
* If ion-back-button is being used inside
21+
* of ion-nav then we should not interact with
22+
* the router.
23+
*/
24+
if (e.target && (e.target as HTMLElement).closest('ion-nav') !== null) {
25+
return;
4926
}
5027

51-
static get contextType() {
52-
return NavContext;
28+
const { defaultHref, routerAnimation } = this.props;
29+
30+
if (this.context.hasIonicRouter()) {
31+
e.stopPropagation();
32+
this.context.goBack(defaultHref, routerAnimation);
33+
} else if (defaultHref !== undefined) {
34+
window.location.href = defaultHref;
5335
}
54-
})();
36+
};
37+
38+
render() {
39+
return <IonBackButtonInner onClick={this.clickButton} {...this.props}></IonBackButtonInner>;
40+
}
41+
42+
static get displayName() {
43+
return 'IonBackButton';
44+
}
45+
46+
static get contextType() {
47+
return NavContext;
48+
}
49+
50+
shouldComponentUpdate(): boolean {
51+
return true;
52+
}
53+
}

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

+36-32
Original file line numberDiff line numberDiff line change
@@ -13,41 +13,45 @@ type Props = LocalJSX.IonTabButton &
1313
onPointerDown?: React.PointerEventHandler<HTMLIonTabButtonElement>;
1414
onTouchEnd?: React.TouchEventHandler<HTMLIonTabButtonElement>;
1515
onTouchMove?: React.TouchEventHandler<HTMLIonTabButtonElement>;
16+
children?: React.ReactNode;
1617
};
1718

18-
export const IonTabButton = /*@__PURE__*/ (() =>
19-
class extends React.Component<Props> {
20-
constructor(props: Props) {
21-
super(props);
22-
this.handleIonTabButtonClick = this.handleIonTabButtonClick.bind(this);
23-
}
19+
export class IonTabButton extends React.Component<Props> {
20+
shouldComponentUpdate(): boolean {
21+
return true;
22+
}
2423

25-
handleIonTabButtonClick() {
26-
if (this.props.onClick) {
27-
this.props.onClick(
28-
new CustomEvent('ionTabButtonClick', {
29-
detail: {
30-
tab: this.props.tab,
31-
href: this.props.href,
32-
routeOptions: this.props.routerOptions,
33-
},
34-
})
35-
);
36-
}
37-
}
24+
constructor(props: Props) {
25+
super(props);
26+
this.handleIonTabButtonClick = this.handleIonTabButtonClick.bind(this);
27+
}
3828

39-
render() {
40-
/**
41-
* onClick is excluded from the props, since it has a custom
42-
* implementation within IonTabBar.tsx. Calling onClick within this
43-
* component would result in duplicate handler calls.
44-
*/
45-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
46-
const { onClick, ...rest } = this.props;
47-
return <IonTabButtonInner onIonTabButtonClick={this.handleIonTabButtonClick} {...rest}></IonTabButtonInner>;
29+
handleIonTabButtonClick() {
30+
if (this.props.onClick) {
31+
this.props.onClick(
32+
new CustomEvent('ionTabButtonClick', {
33+
detail: {
34+
tab: this.props.tab,
35+
href: this.props.href,
36+
routeOptions: this.props.routerOptions,
37+
},
38+
})
39+
);
4840
}
41+
}
4942

50-
static get displayName() {
51-
return 'IonTabButton';
52-
}
53-
})();
43+
render() {
44+
/**
45+
* onClick is excluded from the props, since it has a custom
46+
* implementation within IonTabBar.tsx. Calling onClick within this
47+
* component would result in duplicate handler calls.
48+
*/
49+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
50+
const { onClick, ...rest } = this.props;
51+
return <IonTabButtonInner onIonTabButtonClick={this.handleIonTabButtonClick} {...rest}></IonTabButtonInner>;
52+
}
53+
54+
static get displayName() {
55+
return 'IonTabButton';
56+
}
57+
}

0 commit comments

Comments
 (0)