From 9d0f5a89c6c88343d7ba7326f9b0d791217c7619 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 21 Jul 2024 10:31:03 -0400 Subject: [PATCH] Force eager layout re-calculation after panel added/removed --- .../react-resizable-panels/src/PanelGroup.ts | 74 +++++++++++-------- .../src/hooks/useForceUpdate.ts | 7 ++ 2 files changed, 50 insertions(+), 31 deletions(-) create mode 100644 packages/react-resizable-panels/src/hooks/useForceUpdate.ts diff --git a/packages/react-resizable-panels/src/PanelGroup.ts b/packages/react-resizable-panels/src/PanelGroup.ts index f9d4ac7f0..04c3a9448 100644 --- a/packages/react-resizable-panels/src/PanelGroup.ts +++ b/packages/react-resizable-panels/src/PanelGroup.ts @@ -13,6 +13,7 @@ import { EXCEEDED_VERTICAL_MIN, reportConstraintsViolation, } from "./PanelResizeHandleRegistry"; +import { useForceUpdate } from "./hooks/useForceUpdate"; import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect"; import useUniqueId from "./hooks/useUniqueId"; import { useWindowSplitterPanelGroupBehavior } from "./hooks/useWindowSplitterPanelGroupBehavior"; @@ -123,6 +124,7 @@ function PanelGroupWithForwardedRef({ const panelGroupElementRef = useRef(null); const [dragState, setDragState] = useState(null); const [layout, setLayout] = useState([]); + const forceUpdate = useForceUpdate(); const panelIdToLastNotifiedSizeMapRef = useRef>({}); const panelSizeBeforeCollapseRef = useRef>(new Map()); @@ -521,26 +523,31 @@ function PanelGroupWithForwardedRef({ return !collapsible || fuzzyCompareNumbers(panelSize, collapsedSize) > 0; }, []); - const registerPanel = useCallback((panelData: PanelData) => { - const { panelDataArray } = eagerValuesRef.current; + const registerPanel = useCallback( + (panelData: PanelData) => { + const { panelDataArray } = eagerValuesRef.current; - panelDataArray.push(panelData); - panelDataArray.sort((panelA, panelB) => { - const orderA = panelA.order; - const orderB = panelB.order; - if (orderA == null && orderB == null) { - return 0; - } else if (orderA == null) { - return -1; - } else if (orderB == null) { - return 1; - } else { - return orderA - orderB; - } - }); + panelDataArray.push(panelData); + panelDataArray.sort((panelA, panelB) => { + const orderA = panelA.order; + const orderB = panelB.order; + if (orderA == null && orderB == null) { + return 0; + } else if (orderA == null) { + return -1; + } else if (orderB == null) { + return 1; + } else { + return orderA - orderB; + } + }); - eagerValuesRef.current.panelDataArrayChanged = true; - }, []); + eagerValuesRef.current.panelDataArrayChanged = true; + + forceUpdate(); + }, + [forceUpdate] + ); // (Re)calculate group layout whenever panels are registered or unregistered. // eslint-disable-next-line react-hooks/exhaustive-deps @@ -844,22 +851,27 @@ function PanelGroupWithForwardedRef({ setDragState(null); }, []); - const unregisterPanel = useCallback((panelData: PanelData) => { - const { panelDataArray } = eagerValuesRef.current; + const unregisterPanel = useCallback( + (panelData: PanelData) => { + const { panelDataArray } = eagerValuesRef.current; - const index = findPanelDataIndex(panelDataArray, panelData); - if (index >= 0) { - panelDataArray.splice(index, 1); + const index = findPanelDataIndex(panelDataArray, panelData); + if (index >= 0) { + panelDataArray.splice(index, 1); - // TRICKY - // When a panel is removed from the group, we should delete the most recent prev-size entry for it. - // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted. - // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount. - delete panelIdToLastNotifiedSizeMapRef.current[panelData.id]; + // TRICKY + // When a panel is removed from the group, we should delete the most recent prev-size entry for it. + // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted. + // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount. + delete panelIdToLastNotifiedSizeMapRef.current[panelData.id]; - eagerValuesRef.current.panelDataArrayChanged = true; - } - }, []); + eagerValuesRef.current.panelDataArrayChanged = true; + + forceUpdate(); + } + }, + [forceUpdate] + ); const context = useMemo( () => diff --git a/packages/react-resizable-panels/src/hooks/useForceUpdate.ts b/packages/react-resizable-panels/src/hooks/useForceUpdate.ts new file mode 100644 index 000000000..2f4c389e3 --- /dev/null +++ b/packages/react-resizable-panels/src/hooks/useForceUpdate.ts @@ -0,0 +1,7 @@ +import { useCallback, useState } from "../vendor/react"; + +export function useForceUpdate() { + const [_, setCount] = useState(0); + + return useCallback(() => setCount((prevCount) => prevCount + 1), []); +}