fix(react): fix refs for controllers, overlays, ionpage, and ionrouteroutlet, fixes #19924 (#20012)

This commit is contained in:
Ely Lucas
2019-11-27 16:08:56 -07:00
committed by GitHub
parent 637b082976
commit eef55bb007
6 changed files with 78 additions and 18 deletions

View File

@ -87,6 +87,10 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
ref: this.routerOutletEl ref: this.routerOutletEl
}; };
if(ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = this.routerOutletEl;
}
if (isDevMode()) { if (isDevMode()) {
elementProps['data-stack-id'] = this.id; elementProps['data-stack-id'] = this.id;
} }
@ -99,4 +103,4 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
static get contextType() { static get contextType() {
return RouteManagerContext; return RouteManagerContext;
} }
} }

View File

@ -3,21 +3,34 @@ import React from 'react';
import { NavContext } from '../contexts/NavContext'; import { NavContext } from '../contexts/NavContext';
import { IonicReactProps } from './IonicReactProps'; import { IonicReactProps } from './IonicReactProps';
import { createForwardRef } from './utils';
export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.Component<React.HTMLAttributes<HTMLElement> & IonicReactProps> { interface IonPageProps extends IonicReactProps {
}
interface IonPageInternalProps extends IonPageProps {
forwardedRef?: React.RefObject<HTMLDivElement>;
}
class IonPageInternal extends React.Component<IonPageInternalProps> {
context!: React.ContextType<typeof NavContext>; context!: React.ContextType<typeof NavContext>;
ref = React.createRef<HTMLDivElement>(); ref: React.RefObject<HTMLDivElement>;// React.createRef<HTMLDivElement>();
constructor(props: IonPageInternalProps) {
super(props);
this.ref = this.props.forwardedRef || React.createRef()
}
componentDidMount() { componentDidMount() {
if (this.context && this.ref.current) { if (this.context && this.ref && this.ref.current) {
if (this.context.hasIonicRouter()) { if (this.context.hasIonicRouter()) {
this.context.registerIonPage(this.ref.current); this.context.registerIonPage(this.ref.current);
} }
} }
} }
render() { render() {
const { className, children, ...props } = this.props; const { className, children, forwardedRef, ...props } = this.props;
return ( return (
<div className={className ? `ion-page ${className}` : 'ion-page'} ref={this.ref} {...props}> <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
static get contextType() { static get contextType() {
return NavContext; return NavContext;
} }
})(); };
export const IonPage = createForwardRef(IonPageInternal, 'IonPage');

View File

@ -13,7 +13,7 @@ type Props = LocalJSX.IonRouterOutlet & {
}; };
type InternalProps = Props & { type InternalProps = Props & {
forwardedRef: any; forwardedRef?: React.RefObject<HTMLIonRouterOutletElement>;
}; };
const IonRouterOutletContainer = /*@__PURE__*/(() => class extends React.Component<InternalProps> { const IonRouterOutletContainer = /*@__PURE__*/(() => class extends React.Component<InternalProps> {

View File

@ -1,5 +1,6 @@
export interface IonicReactProps { export interface IonicReactProps {
class?: string; class?: string;
className?: string;
style?: {[key: string]: any }; style?: {[key: string]: any };
} }

View File

@ -19,14 +19,17 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
) => { ) => {
const dismissEventName = `on${displayName}DidDismiss`; const dismissEventName = `on${displayName}DidDismiss`;
type Props = OptionsType & ReactControllerProps; type Props = OptionsType & ReactControllerProps & {
forwardedRef?: React.RefObject<OverlayType>
};
return class extends React.Component<Props> { class Overlay extends React.Component<Props> {
overlay?: OverlayType; overlay?: OverlayType;
isUnmounted = false; isUnmounted = false;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.handleDismiss = this.handleDismiss.bind(this);
} }
static get displayName() { static get displayName() {
@ -54,18 +57,30 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
} }
} }
handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = undefined;
}
}
async present(prevProps?: Props) { async present(prevProps?: Props) {
const { isOpen, onDidDismiss, ...cProps } = this.props; const { isOpen, onDidDismiss, ...cProps } = this.props;
this.overlay = await controller.create({ this.overlay = await controller.create({
...cProps as any ...cProps as any
}); });
attachProps(this.overlay, { attachProps(this.overlay, {
[dismissEventName]: onDidDismiss [dismissEventName]: this.handleDismiss
}, prevProps); }, prevProps);
// Check isOpen again since the value could have changed during the async call to controller.create // Check isOpen again since the value could have changed during the async call to controller.create
// It's also possible for the component to have become unmounted. // It's also possible for the component to have become unmounted.
if (this.props.isOpen === true && this.isUnmounted === false) { if (this.props.isOpen === true && this.isUnmounted === false) {
await this.overlay.present(); if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = this.overlay;
}
await this.overlay.present();
} }
} }
@ -73,4 +88,8 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
return null; return null;
} }
}; };
return React.forwardRef<OverlayType, Props>((props, ref) => {
return <Overlay {...props} forwardedRef={ref} />
})
}; };

View File

@ -15,21 +15,24 @@ export interface ReactOverlayProps {
onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void; onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
} }
export const createOverlayComponent = <T extends object, OverlayType extends OverlayElement>( export const createOverlayComponent = <OverlayComponent extends object, OverlayType extends OverlayElement>(
displayName: string, displayName: string,
controller: { create: (options: any) => Promise<OverlayType> } controller: { create: (options: any) => Promise<OverlayType> }
) => { ) => {
const dismissEventName = `on${displayName}DidDismiss`; const dismissEventName = `on${displayName}DidDismiss`;
type Props = T & ReactOverlayProps; type Props = OverlayComponent & ReactOverlayProps & {
forwardedRef?: React.RefObject<OverlayType>
};
return class extends React.Component<Props> { class Overlay extends React.Component<Props> {
overlay?: OverlayType; overlay?: OverlayType;
el: HTMLDivElement; el: HTMLDivElement;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.el = document.createElement('div'); this.el = document.createElement('div');
this.handleDismiss = this.handleDismiss.bind(this);
} }
static get displayName() { static get displayName() {
@ -46,6 +49,15 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
if (this.overlay) { this.overlay.dismiss(); } if (this.overlay) { this.overlay.dismiss(); }
} }
handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = undefined;
}
}
async componentDidUpdate(prevProps: Props) { async componentDidUpdate(prevProps: Props) {
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) { if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
this.present(prevProps); this.present(prevProps);
@ -56,10 +68,11 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
} }
async present(prevProps?: Props) { async present(prevProps?: Props) {
const { children, isOpen, onDidDismiss = () => { return; }, ...cProps } = this.props; const { children, isOpen, onDidDismiss, ...cProps } = this.props;
const elementProps = { const elementProps = {
...cProps, ...cProps,
[dismissEventName]: onDidDismiss ref: this.props.forwardedRef,
[dismissEventName]: this.handleDismiss
}; };
const overlay = this.overlay = await controller.create({ const overlay = this.overlay = await controller.create({
@ -68,6 +81,10 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
componentProps: {} componentProps: {}
}); });
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = overlay;
}
attachProps(overlay, elementProps, prevProps); attachProps(overlay, elementProps, prevProps);
await overlay.present(); await overlay.present();
@ -76,8 +93,12 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
render() { render() {
return ReactDOM.createPortal( return ReactDOM.createPortal(
this.props.children, this.props.children,
this.el, this.el
); );
} }
}; };
return React.forwardRef<OverlayType, Props>((props, ref) => {
return <Overlay {...props} forwardedRef={ref} />
})
}; };