From 11b037e084acec94c2b5ab7e0b62313e40d00118 Mon Sep 17 00:00:00 2001 From: Axel Bocciarelli Date: Thu, 15 Oct 2026 14:04:33 +0200 Subject: [PATCH 1/2] Make slicing slider a controlled component --- .../src/dimension-mapper/DimensionMapper.tsx | 2 +- .../src/dimension-mapper/SlicingSlider.tsx | 35 ++++++++++--------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/lib/src/dimension-mapper/DimensionMapper.tsx b/packages/lib/src/dimension-mapper/DimensionMapper.tsx index c2f789bdd..97d74e470 100644 --- a/packages/lib/src/dimension-mapper/DimensionMapper.tsx +++ b/packages/lib/src/dimension-mapper/DimensionMapper.tsx @@ -68,7 +68,7 @@ function DimensionMapper(props: Props) { key={index} // eslint-disable-line react/no-array-index-key dimension={index} length={dims[index]} - initialValue={val} + value={val} withControls={withSlicingControls} isFastSlice={ canSliceFast && diff --git a/packages/lib/src/dimension-mapper/SlicingSlider.tsx b/packages/lib/src/dimension-mapper/SlicingSlider.tsx index 128722907..009f081a1 100644 --- a/packages/lib/src/dimension-mapper/SlicingSlider.tsx +++ b/packages/lib/src/dimension-mapper/SlicingSlider.tsx @@ -15,21 +15,15 @@ const LONG_DELAY = 250; interface Props { dimension: number; length: number; - initialValue: number; + value: number; withControls: boolean; isFastSlice?: (value: number) => boolean; onChange: (value: number) => void; } function SlicingSlider(props: Props) { - const { - dimension, - length, - initialValue, - withControls, - isFastSlice, - onChange, - } = props; + const { dimension, length, value, withControls, isFastSlice, onChange } = + props; if (length <= 0) { throw new Error('Expected non-empty dimension'); @@ -38,13 +32,20 @@ function SlicingSlider(props: Props) { const [stride, setStride] = useState(1); const strideInputId = useId(); - const [value, setValue] = useState(initialValue); + const [localValue, setLocalValue] = useState(value); const onDebouncedChange = useDynamicDebouncedCallback( onChange, [onChange], (val) => (isFastSlice?.(val) ? SHORT_DELAY : LONG_DELAY), ); + // When the controlled value changes, adopt it during render + const [prevValue, setPrevValue] = useState(value); + if (value !== prevValue) { + setPrevValue(value); + setLocalValue(value); + } + const [containerSize, containerRef] = useMeasure(); const sliderLabelId = `${ID}-${dimension}-label`; @@ -69,9 +70,9 @@ function SlicingSlider(props: Props) { markClassName={styles.mark} orientation="vertical" invert - value={value} + value={localValue} onChange={(newValue) => { - setValue(newValue); + setLocalValue(newValue); onDebouncedChange(newValue); }} /* When slicing in E2E tests, `onChange` is called with the old value. @@ -79,7 +80,7 @@ function SlicingSlider(props: Props) { https://github.com/zillow/react-slider/blob/master/src/components/ReactSlider/ReactSlider.jsx#L908 Adding `onAfterChange` fixes the issue for now with react-slider@2.0.4, but not with react-slider@2.0.5+ */ onAfterChange={(newValue) => { - setValue(newValue); + setLocalValue(newValue); onDebouncedChange(newValue); }} renderThumb={({ key, ...thumbProps }, state) => ( @@ -100,8 +101,8 @@ function SlicingSlider(props: Props) { iconOnly small onClick={() => { - const newValue = Math.min(value + stride, length - 1); - setValue(newValue); + const newValue = Math.min(localValue + stride, length - 1); + setLocalValue(newValue); onDebouncedChange(newValue); }} /> @@ -111,8 +112,8 @@ function SlicingSlider(props: Props) { iconOnly small onClick={() => { - const newValue = Math.max(value - stride, 0); - setValue(newValue); + const newValue = Math.max(localValue - stride, 0); + setLocalValue(newValue); onDebouncedChange(newValue); }} /> From 2cf112ba7037c2129e4642bc14fdb3967058ec9c Mon Sep 17 00:00:00 2001 From: Axel Bocciarelli Date: Thu, 15 Oct 2026 14:22:24 +0200 Subject: [PATCH 2/2] Support changing default stride of slicing slider --- .../lib/src/dimension-mapper/DimensionMapper.tsx | 3 +++ .../lib/src/dimension-mapper/SlicingSlider.tsx | 14 +++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/lib/src/dimension-mapper/DimensionMapper.tsx b/packages/lib/src/dimension-mapper/DimensionMapper.tsx index 97d74e470..12620caff 100644 --- a/packages/lib/src/dimension-mapper/DimensionMapper.tsx +++ b/packages/lib/src/dimension-mapper/DimensionMapper.tsx @@ -10,6 +10,7 @@ interface Props extends Omit, 'onChange'> { dimHints?: (string | undefined)[]; dimMapping: DimensionMapping; withSlicingControls?: boolean; + defaultStride?: number; canSliceFast?: (nextMapping: DimensionMapping) => boolean; onChange: (d: DimensionMapping) => void; } @@ -21,6 +22,7 @@ function DimensionMapper(props: Props) { dimHints, dimMapping, withSlicingControls = false, + defaultStride, canSliceFast, onChange, ...htmlProps @@ -70,6 +72,7 @@ function DimensionMapper(props: Props) { length={dims[index]} value={val} withControls={withSlicingControls} + defaultStride={defaultStride} isFastSlice={ canSliceFast && ((newVal) => { diff --git a/packages/lib/src/dimension-mapper/SlicingSlider.tsx b/packages/lib/src/dimension-mapper/SlicingSlider.tsx index 009f081a1..589ded075 100644 --- a/packages/lib/src/dimension-mapper/SlicingSlider.tsx +++ b/packages/lib/src/dimension-mapper/SlicingSlider.tsx @@ -17,19 +17,27 @@ interface Props { length: number; value: number; withControls: boolean; + defaultStride?: number; isFastSlice?: (value: number) => boolean; onChange: (value: number) => void; } function SlicingSlider(props: Props) { - const { dimension, length, value, withControls, isFastSlice, onChange } = - props; + const { + dimension, + length, + value, + withControls, + defaultStride = 1, + isFastSlice, + onChange, + } = props; if (length <= 0) { throw new Error('Expected non-empty dimension'); } - const [stride, setStride] = useState(1); + const [stride, setStride] = useState(defaultStride); const strideInputId = useId(); const [localValue, setLocalValue] = useState(value);