Skip to content

Commit

Permalink
fix(ResizablePanel): infinite loop in controllable mode (#543)
Browse files Browse the repository at this point in the history
  • Loading branch information
tenphi authored Dec 12, 2024
1 parent d22e020 commit b813f22
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/smooth-donuts-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cube-dev/ui-kit': patch
---

Prevent ResizablePanel from infinite switching state loop in controllable mode.
1 change: 0 additions & 1 deletion src/components/layout/ResizablePanel.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ const TemplateTop: StoryFn<CubeResizablePanelProps> = (args) => {

const TemplateControllable: StoryFn<CubeResizablePanelProps> = (args) => {
const [size, setSize] = useState(200);

return (
<ResizablePanel
size={size}
Expand Down
56 changes: 36 additions & 20 deletions src/components/layout/ResizablePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { ForwardedRef, forwardRef, useEffect, useMemo, useState } from 'react';
import { useHover, useMove } from 'react-aria';

import { BasePropsWithoutChildren, Styles, tasty } from '../../tasty/index';
import { mergeProps, useCombinedRefs } from '../../utils/react/index';
import { mergeProps, useCombinedRefs } from '../../utils/react';
import { useEvent } from '../../_internal/hooks';

import { Panel, CubePanelProps } from './Panel';

Expand Down Expand Up @@ -217,19 +218,6 @@ function ResizablePanel(
);
let [visualSize, setVisualSize] = useState<number | null>(null);

useEffect(() => {
if (ref.current) {
const offsetProp = isHorizontal ? 'offsetWidth' : 'offsetHeight';
const containerSize = ref.current[offsetProp];

if (Math.abs(containerSize - size) < 1 && !isDisabled) {
setVisualSize(size);
} else {
setVisualSize(containerSize);
}
}
}, [size, isDisabled]);

let { moveProps } = useMove({
onMoveStart(e) {
if (isDisabled) {
Expand Down Expand Up @@ -258,7 +246,7 @@ function ResizablePanel(
? e.deltaX * (direction === 'right' ? 1 : -1)
: e.deltaY * (direction === 'bottom' ? 1 : -1);

return clamp(size);
return size;
});
},
onMoveEnd(e) {
Expand All @@ -267,16 +255,44 @@ function ResizablePanel(
},
});

// Since we sync provided size and the local one in two ways
// we need a way to prevent infinite loop in some cases.
// We will run this in setTimeout and make sure it will get the most recent state.
const notifyChange = useEvent(() => {
setSize((size) => {
if (providedSize && Math.abs(providedSize - size) > 0.5) {
return providedSize;
}

return size;
});
});

useEffect(() => {
if (providedSize == null || Math.abs(providedSize - size) > 0.5) {
onSizeChange?.(Math.round(size));
if (ref.current) {
const offsetProp = isHorizontal ? 'offsetWidth' : 'offsetHeight';
const containerSize = ref.current[offsetProp];

if (Math.abs(containerSize - size) < 1 && !isDisabled) {
setVisualSize(size);
} else {
setVisualSize(containerSize);
}
}
}, [size]);
}, [size, isDisabled]);

useEffect(() => {
if (providedSize && Math.abs(providedSize - size) > 0.5) {
setSize(clamp(providedSize));
if (
!isDragging &&
visualSize != null &&
(providedSize == null || Math.abs(providedSize - visualSize) > 0.5)
) {
onSizeChange?.(Math.round(visualSize));
}
}, [visualSize]);

useEffect(() => {
setTimeout(notifyChange);
}, [providedSize]);

const mods = useMemo(() => {
Expand Down

0 comments on commit b813f22

Please sign in to comment.