javascript 64 lines · 8 steps

Building a dual-thumb price slider in React

A two-handle range slider that keeps its min and max from ever crossing, using clamped state updates.

Explained by highlit
1import { useCallback, useState } from "react";
2 
3const MIN = 0;
4const MAX = 1000;
5const GAP = 50;
6 
7export default function PriceRangeSlider({ onChange }) {
8 const [range, setRange] = useState({ min: 200, max: 800 });
9 
10 const updateMin = useCallback((value) => {
11 setRange((prev) => {
12 const next = Math.min(Math.max(value, MIN), prev.max - GAP);
13 const updated = { ...prev, min: next };
14 onChange?.(updated);
15 return updated;
16 });
17 }, [onChange]);
18 
19 const updateMax = useCallback((value) => {
20 setRange((prev) => {
21 const next = Math.max(Math.min(value, MAX), prev.min + GAP);
22 const updated = { ...prev, max: next };
23 onChange?.(updated);
24 return updated;
25 });
26 }, [onChange]);
27 
28 const toPercent = (value) => ((value - MIN) / (MAX - MIN)) * 100;
29 
30 return (
31 <div className="range-slider">
32 <div className="range-slider__track">
33 <div
34 className="range-slider__fill"
35 style={{
36 left: `${toPercent(range.min)}%`,
37 right: `${100 - toPercent(range.max)}%`,
38 }}
39 />
40 </div>
41 <input
42 type="range"
43 min={MIN}
44 max={MAX}
45 value={range.min}
46 onChange={(e) => updateMin(Number(e.target.value))}
47 className="range-slider__thumb range-slider__thumb--min"
48 aria-label="Minimum price"
49 />
50 <input
51 type="range"
52 min={MIN}
53 max={MAX}
54 value={range.max}
55 onChange={(e) => updateMax(Number(e.target.value))}
56 className="range-slider__thumb range-slider__thumb--max"
57 aria-label="Maximum price"
58 />
59 <output className="range-slider__values">
60 ${range.min} ${range.max}
61 </output>
62 </div>
63 );
64}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Clamping values inside the state updater keeps two dependent handles from crossing each other.
  2. 2Deriving both handles from one state object keeps the min and max always consistent.
  3. 3Optional-chaining a callback lets a component report changes without requiring the parent to supply a handler.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a dual-thumb price slider in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code