Need to get the last value of onChange for a slider React

Viewed 7661

I'm using material ui sliders to get values before querying. Is there a way of only getting the values of the slider when it has stopped moving instead of every time it moves? I want to avoid using a submit button if possible.

Here is a link to the material ui demo, they use onChange to capture the values. I only need the values when the slider has stopped and not when it is changing.

https://codesandbox.io/s/material-demo-sss3v

3 Answers

I might just do a simple timeout for something like this. You can just determine what you think is appropriate for when they have "stopped", after about a second seems to work okay.

export default function RangeSlider() {
  var timeout;
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);

  const handleChange = (event, newValue) => {
    timeout && clearTimeout(timeout);
    timeout = setTimeout(() => {
      console.log('change');
      setValue(newValue);  
    }, 1000);
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
      />
    </div>
  );
}```

The only way to know if the user is done is to use onChangeCommitted but that only works when the user uses a mouse to interact. If the user uses arrow keys than onChangeCommitted will trigger for every change.

You could wait for a certain amount of time before doing the search. If there are multiple search term the user can change then you can use useDebounce demonstrated here code is in demo.js

const [search, setSearch] = React.useState({ slider1: 10, slider2: 10 });
const onChange = React.useCallback(
  (key, val) => setSearch(search => ({ ...search, [key]: val })),
  []
);
//start searching when user did not change anything for 5 second
//  I know it's a long time but demonstrates behavior more easily
const debouncedSearch = useDebounce(search, 5000);
// Effect for API call
React.useEffect(
  () => {
    console.log("user inactive for five seconds, going to search now");
  },
  [debouncedSearch] // Only call effect if debounced search term changes
);

If you do fire a request based on user input then you should make sure to only do something on the response of the last user input. You can do this with the effect and cancelled demonstrated in the code above and here. If you don't you can get some unexpected race conditions.

If you use the timeout approach you'll notice that your slider animation only updates at one second intervals. The best approach is to add the additional event handler onChangeCommitted. This way the value will stay updated on the slider.

export default function RangeSlider() {
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);

  const handleChange = (event, newValue) => {
      setValue(newValue);  
  };

  const handleChangeCommitted = (event, newValue) => {
      // Here will only be triggered once the user releases the slider.  
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        onChangeCommitted={handleChangeCommitted}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
      />
    </div>
  );
}

Note that if the user uses the arrow keys to adjust the slider, the onChangeCommitted handler will be called at every increment.

Related