fix(react): overlays shown with useIonModal and useIonPopover no longer render outside of main react tree

closes #23516 and #23516
This commit is contained in:
Ely Lucas
2021-10-15 15:29:25 -06:00
committed by GitHub
parent 3451a34ad0
commit f3e492c897
13 changed files with 197 additions and 46 deletions

View File

@ -1,8 +1,10 @@
import { ModalOptions, modalController } from '@ionic/core/components';
import { useCallback } from 'react';
import { ReactComponentOrElement } from '../models/ReactComponentOrElement';
import { HookOverlayOptions } from './HookOverlayOptions';
import { ReactComponentOrElement, useOverlay } from './useOverlay';
import { useOverlay } from './useOverlay';
/**
* A hook for presenting/dismissing an IonModal component

View File

@ -1,8 +1,10 @@
import { PopoverOptions, popoverController } from '@ionic/core/components';
import { useCallback } from 'react';
import { ReactComponentOrElement } from '../models/ReactComponentOrElement';
import { HookOverlayOptions } from './HookOverlayOptions';
import { ReactComponentOrElement, useOverlay } from './useOverlay';
import { useOverlay } from './useOverlay';
/**
* A hook for presenting/dismissing an IonPicker component

View File

@ -1,53 +1,41 @@
import { OverlayEventDetail } from '@ionic/core/components';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { attachProps } from '../components/react-component-lib/utils';
import { IonContext } from '../contexts/IonContext';
import { ReactComponentOrElement } from '../models/ReactComponentOrElement';
import { generateId } from '../utils/generateId';
import { HookOverlayOptions } from './HookOverlayOptions';
export type ReactComponentOrElement = React.ComponentClass<any, any> | React.FC<any> | JSX.Element;
interface OverlayBase extends HTMLElement {
present: () => Promise<void>;
dismiss: (data?: any, role?: string | undefined) => Promise<boolean>;
}
export function useOverlay<
OptionsType,
OverlayType extends OverlayBase
>(
export function useOverlay<OptionsType, OverlayType extends OverlayBase>(
displayName: string,
controller: { create: (options: OptionsType) => Promise<OverlayType>; },
controller: { create: (options: OptionsType) => Promise<OverlayType> },
component: ReactComponentOrElement,
componentProps?: any
) {
const overlayRef = useRef<OverlayType>();
const containerElRef = useRef<HTMLDivElement>();
const didDismissEventName = useMemo(
() => `on${displayName}DidDismiss`,
[displayName]
);
const didPresentEventName = useMemo(
() => `on${displayName}DidPresent`,
[displayName]
);
const willDismissEventName = useMemo(
() => `on${displayName}WillDismiss`,
[displayName]
);
const willPresentEventName = useMemo(
() => `on${displayName}WillPresent`,
[displayName]
);
const didDismissEventName = useMemo(() => `on${displayName}DidDismiss`, [displayName]);
const didPresentEventName = useMemo(() => `on${displayName}DidPresent`, [displayName]);
const willDismissEventName = useMemo(() => `on${displayName}WillDismiss`, [displayName]);
const willPresentEventName = useMemo(() => `on${displayName}WillPresent`, [displayName]);
const [isOpen, setIsOpen] = useState(false);
const ionContext = useContext(IonContext);
const [overlayId] = useState(generateId('overlay'));
useEffect(() => {
if (isOpen && component && containerElRef.current) {
if (React.isValidElement(component)) {
ReactDOM.render(component, containerElRef.current);
ionContext.addOverlay(overlayId, component, containerElRef.current!);
} else {
ReactDOM.render(React.createElement(component as React.ComponentClass, componentProps), containerElRef.current);
const element = React.createElement(component as React.ComponentClass, componentProps);
ionContext.addOverlay(overlayId, element, containerElRef.current!);
}
}
}, [component, containerElRef.current, isOpen, componentProps]);
@ -57,13 +45,7 @@ export function useOverlay<
return;
}
const {
onDidDismiss,
onWillDismiss,
onDidPresent,
onWillPresent,
...rest
} = options;
const { onDidDismiss, onWillDismiss, onDidPresent, onWillPresent, ...rest } = options;
if (typeof document !== 'undefined') {
containerElRef.current = document.createElement('div');
@ -71,17 +53,14 @@ export function useOverlay<
overlayRef.current = await controller.create({
...(rest as any),
component: containerElRef.current
component: containerElRef.current,
});
attachProps(overlayRef.current, {
[didDismissEventName]: handleDismiss,
[didPresentEventName]: (e: CustomEvent) =>
onDidPresent && onDidPresent(e),
[willDismissEventName]: (e: CustomEvent) =>
onWillDismiss && onWillDismiss(e),
[willPresentEventName]: (e: CustomEvent) =>
onWillPresent && onWillPresent(e),
[didPresentEventName]: (e: CustomEvent) => onDidPresent && onDidPresent(e),
[willDismissEventName]: (e: CustomEvent) => onWillDismiss && onWillDismiss(e),
[willPresentEventName]: (e: CustomEvent) => onWillPresent && onWillPresent(e),
});
overlayRef.current.present();
@ -95,11 +74,12 @@ export function useOverlay<
overlayRef.current = undefined;
containerElRef.current = undefined;
setIsOpen(false);
ionContext.removeOverlay(overlayId);
}
}, []);
const dismiss = useCallback(async () => {
overlayRef.current && await overlayRef.current.dismiss();
overlayRef.current && (await overlayRef.current.dismiss());
overlayRef.current = undefined;
containerElRef.current = undefined;
}, []);