Skip to content

Commit ab0f92e

Browse files
elylucasEly Lucas
authored andcommitted
fix(react): fix refs for controllers, overlays, ionpage, and ionrouteroutlet, fixes #19924 (#20012)
1 parent 93bd4af commit ab0f92e

File tree

6 files changed

+78
-18
lines changed

6 files changed

+78
-18
lines changed

packages/react-router/src/ReactRouter/StackManager.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
8787
ref: this.routerOutletEl
8888
};
8989

90+
if(ionRouterOutlet.props.forwardedRef) {
91+
ionRouterOutlet.props.forwardedRef.current = this.routerOutletEl;
92+
}
93+
9094
if (isDevMode()) {
9195
elementProps['data-stack-id'] = this.id;
9296
}
@@ -99,4 +103,4 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
99103
static get contextType() {
100104
return RouteManagerContext;
101105
}
102-
}
106+
}

packages/react/src/components/IonPage.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,34 @@ import React from 'react';
33
import { NavContext } from '../contexts/NavContext';
44

55
import { IonicReactProps } from './IonicReactProps';
6+
import { createForwardRef } from './utils';
67

7-
export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.Component<React.HTMLAttributes<HTMLElement> & IonicReactProps> {
8+
interface IonPageProps extends IonicReactProps {
9+
}
10+
11+
interface IonPageInternalProps extends IonPageProps {
12+
forwardedRef?: React.RefObject<HTMLDivElement>;
13+
}
14+
15+
class IonPageInternal extends React.Component<IonPageInternalProps> {
816
context!: React.ContextType<typeof NavContext>;
9-
ref = React.createRef<HTMLDivElement>();
17+
ref: React.RefObject<HTMLDivElement>;// React.createRef<HTMLDivElement>();
18+
19+
constructor(props: IonPageInternalProps) {
20+
super(props);
21+
this.ref = this.props.forwardedRef || React.createRef()
22+
}
1023

1124
componentDidMount() {
12-
if (this.context && this.ref.current) {
25+
if (this.context && this.ref && this.ref.current) {
1326
if (this.context.hasIonicRouter()) {
1427
this.context.registerIonPage(this.ref.current);
1528
}
16-
}
29+
}
1730
}
1831

1932
render() {
20-
const { className, children, ...props } = this.props;
33+
const { className, children, forwardedRef, ...props } = this.props;
2134

2235
return (
2336
<div className={className ? `ion-page ${className}` : 'ion-page'} ref={this.ref} {...props}>
@@ -33,4 +46,6 @@ export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.C
3346
static get contextType() {
3447
return NavContext;
3548
}
36-
})();
49+
};
50+
51+
export const IonPage = createForwardRef(IonPageInternal, 'IonPage');

packages/react/src/components/IonRouterOutlet.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type Props = LocalJSX.IonRouterOutlet & {
1313
};
1414

1515
type InternalProps = Props & {
16-
forwardedRef: any;
16+
forwardedRef?: React.RefObject<HTMLIonRouterOutletElement>;
1717
};
1818

1919
const IonRouterOutletContainer = /*@__PURE__*/(() => class extends React.Component<InternalProps> {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
export interface IonicReactProps {
33
class?: string;
4+
className?: string;
45
style?: {[key: string]: any };
56
}

packages/react/src/components/createControllerComponent.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
1919
) => {
2020
const dismissEventName = `on${displayName}DidDismiss`;
2121

22-
type Props = OptionsType & ReactControllerProps;
22+
type Props = OptionsType & ReactControllerProps & {
23+
forwardedRef?: React.RefObject<OverlayType>
24+
};
2325

24-
return class extends React.Component<Props> {
26+
class Overlay extends React.Component<Props> {
2527
overlay?: OverlayType;
2628
isUnmounted = false;
2729

2830
constructor(props: Props) {
2931
super(props);
32+
this.handleDismiss = this.handleDismiss.bind(this);
3033
}
3134

3235
static get displayName() {
@@ -54,23 +57,39 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
5457
}
5558
}
5659

60+
handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
61+
if (this.props.onDidDismiss) {
62+
this.props.onDidDismiss(event);
63+
}
64+
if (this.props.forwardedRef) {
65+
(this.props.forwardedRef as any).current = undefined;
66+
}
67+
}
68+
5769
async present(prevProps?: Props) {
5870
const { isOpen, onDidDismiss, ...cProps } = this.props;
5971
this.overlay = await controller.create({
6072
...cProps as any
6173
});
6274
attachProps(this.overlay, {
63-
[dismissEventName]: onDidDismiss
75+
[dismissEventName]: this.handleDismiss
6476
}, prevProps);
6577
// Check isOpen again since the value could have changed during the async call to controller.create
6678
// It's also possible for the component to have become unmounted.
6779
if (this.props.isOpen === true && this.isUnmounted === false) {
68-
await this.overlay.present();
80+
if (this.props.forwardedRef) {
81+
(this.props.forwardedRef as any).current = this.overlay;
82+
}
83+
await this.overlay.present();
6984
}
7085
}
7186

7287
render(): null {
7388
return null;
7489
}
7590
};
91+
92+
return React.forwardRef<OverlayType, Props>((props, ref) => {
93+
return <Overlay {...props} forwardedRef={ref} />
94+
})
7695
};

packages/react/src/components/createOverlayComponent.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,24 @@ export interface ReactOverlayProps {
1515
onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
1616
}
1717

18-
export const createOverlayComponent = <T extends object, OverlayType extends OverlayElement>(
18+
export const createOverlayComponent = <OverlayComponent extends object, OverlayType extends OverlayElement>(
1919
displayName: string,
2020
controller: { create: (options: any) => Promise<OverlayType> }
2121
) => {
2222
const dismissEventName = `on${displayName}DidDismiss`;
2323

24-
type Props = T & ReactOverlayProps;
24+
type Props = OverlayComponent & ReactOverlayProps & {
25+
forwardedRef?: React.RefObject<OverlayType>
26+
};
2527

26-
return class extends React.Component<Props> {
28+
class Overlay extends React.Component<Props> {
2729
overlay?: OverlayType;
2830
el: HTMLDivElement;
2931

3032
constructor(props: Props) {
3133
super(props);
3234
this.el = document.createElement('div');
35+
this.handleDismiss = this.handleDismiss.bind(this);
3336
}
3437

3538
static get displayName() {
@@ -46,6 +49,15 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
4649
if (this.overlay) { this.overlay.dismiss(); }
4750
}
4851

52+
handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
53+
if (this.props.onDidDismiss) {
54+
this.props.onDidDismiss(event);
55+
}
56+
if (this.props.forwardedRef) {
57+
(this.props.forwardedRef as any).current = undefined;
58+
}
59+
}
60+
4961
async componentDidUpdate(prevProps: Props) {
5062
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
5163
this.present(prevProps);
@@ -56,10 +68,11 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
5668
}
5769

5870
async present(prevProps?: Props) {
59-
const { children, isOpen, onDidDismiss = () => { return; }, ...cProps } = this.props;
71+
const { children, isOpen, onDidDismiss, ...cProps } = this.props;
6072
const elementProps = {
6173
...cProps,
62-
[dismissEventName]: onDidDismiss
74+
ref: this.props.forwardedRef,
75+
[dismissEventName]: this.handleDismiss
6376
};
6477

6578
const overlay = this.overlay = await controller.create({
@@ -68,6 +81,10 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
6881
componentProps: {}
6982
});
7083

84+
if (this.props.forwardedRef) {
85+
(this.props.forwardedRef as any).current = overlay;
86+
}
87+
7188
attachProps(overlay, elementProps, prevProps);
7289

7390
await overlay.present();
@@ -76,8 +93,12 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
7693
render() {
7794
return ReactDOM.createPortal(
7895
this.props.children,
79-
this.el,
96+
this.el
8097
);
8198
}
8299
};
100+
101+
return React.forwardRef<OverlayType, Props>((props, ref) => {
102+
return <Overlay {...props} forwardedRef={ref} />
103+
})
83104
};

0 commit comments

Comments
 (0)