fix(react): add useRef wrapper to useIonOverlay state to avoid stale references (#24553)

This commit is contained in:
Amanda Smith
2022-01-11 13:40:46 -06:00
committed by GitHub
parent bb9e5f68b4
commit bce849c5f3

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { ReactComponentOrElement } from '../models'; import { ReactComponentOrElement } from '../models';
@ -25,12 +25,29 @@ export const IonOverlayManager: React.FC<IonOverlayManagerProps> = ({
onAddOverlay, onAddOverlay,
onRemoveOverlay, onRemoveOverlay,
}) => { }) => {
const [overlays, setOverlays] = useState<{ type OverlaysList = {
[key: string]: { [key: string]: {
component: any; component: any;
containerElement: HTMLDivElement; containerElement: HTMLDivElement;
}; };
}>({}); };
/**
* Because of the way we're passing around the addOverlay and removeOverlay
* callbacks, by the time they finally get called, they use a stale reference
* to the state that only has the initial values. So if two overlays are opened
* at the same time, both using useIonModal or similar (such as through nesting),
* the second will erase the first from the overlays list. This causes the content
* of the first overlay to unmount.
*
* We wrap the state in useRef to ensure the two callbacks always use the most
* up-to-date version.
*
* Further reading: https://stackoverflow.com/a/56554056
*/
const [overlays, setOverlays] = useState<OverlaysList>({});
const overlaysRef = useRef<OverlaysList>({});
overlaysRef.current = overlays;
useEffect(() => { useEffect(() => {
/* Setup the callbacks that get called from <IonApp /> */ /* Setup the callbacks that get called from <IonApp /> */
@ -43,13 +60,13 @@ export const IonOverlayManager: React.FC<IonOverlayManagerProps> = ({
component: ReactComponentOrElement, component: ReactComponentOrElement,
containerElement: HTMLDivElement containerElement: HTMLDivElement
) => { ) => {
const newOverlays = { ...overlays }; const newOverlays = { ...overlaysRef.current };
newOverlays[id] = { component, containerElement }; newOverlays[id] = { component, containerElement };
setOverlays(newOverlays); setOverlays(newOverlays);
}; };
const removeOverlay = (id: string) => { const removeOverlay = (id: string) => {
const newOverlays = { ...overlays }; const newOverlays = { ...overlaysRef.current };
delete newOverlays[id]; delete newOverlays[id];
setOverlays(newOverlays); setOverlays(newOverlays);
}; };