Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create Range Slider in React.js

I'm trying to create a range slider in ReactJS

rangeSlider.jsx

const RangeSlider = ({onChange}) => {

    const [slider, setSlider] = useState({
        max: 100, 
        min: 0, 
        value: 0, 
        label: ''
    });

    const onSlide = () => {
        onChange(slider.value);
    } 

    return (
        <div className="range-slider">
            <p>{slider.label}</p>
            <input type="range" min={slider.min} max={slider.max} value={slider.value} 
             onChange={() => onSlide()} className="slider" id="myRange"></input>
        </div>
    );
}
export default RangeSlider;

then I use it in other components

 <RangeSlider onChange={(value) => sliderValueChanged(value)} />
  • If I wanted to pass in a custom label, how would I update the state to do that?
  • Do I have to use React.memo for this? My understanding, every time the slider value changes it creates a new instance of the slider.
  • I'd like this to be robust (steps, multi-handles, tool-tips, etc.) eventually, any help is appreciated.
like image 747
Skeeter62889 Avatar asked Oct 21 '25 07:10

Skeeter62889


1 Answers

When you want to create a reusable component, always try to pass the configuration from where it's uses and keep all common configurations inside the component

EX: Read about how useMemo and useReducer works

useMemo

useReducer

const App = () => {
  //Keep slider value in parent
  const [parentVal, setParentVal] = useState(10);

  //need useCallback why? if this component rendered we don't want to recreate the onChange function
  const sliderValueChanged = useCallback(val => {
    console.log("NEW VALUE", val);
    setParentVal(val);
  });

  // need useMemo why? if this component rendered we don't want to recreate a new instance of the configuration object,
 // but recreate it when parentVal gets changed, so Slider will re-render,
 // and you can remove parentVal from dependency array and once the parent parentVal gets updated slider will not be re-renderd
  const sliderProps = useMemo(
    () => ({
      min: 0,
      max: 100,
      value: parentVal,
      step: 2,
      label: "This is a reusable slider",
      onChange: e => sliderValueChanged(e)
    }),
    // dependency array, this will call useMemo function only when parentVal gets changed,
    // if you 100% confident parentVal only updated from Slider, then you can keep empty dependency array
    // and it will not re-render for any configuration object change 
    [parentVal]
  );

  return (
    <div>
      <h1>PARENT VALUE: {parentVal}</h1>
      <RangeSlider {...sliderProps} classes="additional-css-classes" />
    </div>
  );
};

and in Slider component

//destructive props
const RangeSlider = ({ classes, label, onChange, value, ...sliderProps }) => {
     //set initial value to 0 this will change inside useEffect in first render also| or you can directly set useState(value)
    const [sliderVal, setSliderVal] = useState(0);

    // keep mouse state to determine whether i should call parent onChange or not.
    // so basically after dragging the slider and then release the mouse then we will call the parent onChange, otherwise parent function will get call each and every change
    const [mouseState, setMouseState] = useState(null);

    useEffect(() => {
      setSliderVal(value); // set new value when value gets changed, even when first render
    }, [value]);

    const changeCallback = (e) => {
      setSliderVal(e.target.value); // update local state of the value when changing
    }

    useEffect(() => {
      if (mouseState === "up") {
        onChange(sliderVal)// when mouse is up then call the parent onChange
      }
    }, [mouseState])

    return (
      <div className="range-slider">
        <p>{label}</p>
        <h3>value: { sliderVal }</h3>
        <input
          type="range"
          value={sliderVal}
          {...sliderProps}
          className={`slider ${classes}`}
          id="myRange"
          onChange={changeCallback}
          onMouseDown={() => setMouseState("down")} // When mouse down set the mouseState to 'down'
          onMouseUp={() => setMouseState("up")} // When mouse down set the mouseState to 'up' | now we can call the parent onChnage
        />
      </div>
    );
};

export default memo(RangeSlider);

check my demo

I guess this answer call the 3 questions

  1. use configuration in parent to pass the non-common configuration like label

  2. Use memo ? Yes, so Slider component will only get rendered only when the props gets changed. But you have to carefully design it (ex: useMemo and useCallback)

  3. steps ? use configuration object in parent to pass these.


Just in case if you need a nice way to wrap a range i would suggest you to use a custom hook

const useSlider = ({ value, ...config }) => {
  const [sliderVal, setSliderVal] = useState(value); // keep a state for each slider

  const [configuration, setConfiguration] = useState(config); // keep the configuration for each slider

  const onChange = useCallback(val => {
      setSliderVal(val);
  // useCallback why? we dont need to recreate every time this hook gets called
  }, []);

  useEffect(() => {
    setConfiguration({
      ...config,
      onChange,
      value: sliderVal
    });
  // when sliderVal gets changed call this effect
  // and return a new configuration, so the slider can rerender with latest configuration
  }, [sliderVal]);

  return [sliderVal, configuration];
};

Here is a demo

This might can be further improvement

like image 194
Kalhan.Toress Avatar answered Oct 23 '25 23:10

Kalhan.Toress