最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do I lower the rate of http requests triggered due to onChange for Material UI Slider? - Stack Overflow

programmeradmin3浏览0评论

How do I sort of buffer my requests?

Currently I am using Material UI's slider in my React project. I am using the onChange property to trigger a http post requests when I move along the slider. The code looks something like this.

<Slider
          onChange={handleChange}
          valueLabelFormat={valueLabelFormat}
          //   aria-labelledby="discrete-slider"
          aria-labelledby="non-linear-slider"
          valueLabelDisplay="auto"
          value={value}
          min={1}
          max={5}
          step={0.05}
        />

However, if you can imagine, the onChange will be too frequent and there will be too many HTTP requests being sent. This is definitely not remended. I am also not intending to use onChangeCommitted as it is not my desired way of interaction.

So this is what I want to do:
IF I move along the slider (or onChange), onChange will continually update the value, but http requests will only be triggered every 500ms (with the latest value updated with onChange) . Rather than triggering a request for every onChange!
I have totally no idea how to achieve this..... setInterval or..? Not really sure. Help will be appreciated.
Thank you!


UPDATE
The answer is to use debounce (look at the answer below by k.s.)
However my use case is slightly different from the usual use cases. The function that I am using in my debounce requires inputs to it. I am using debounce inside my handleChange() function instead as I need the handleChange to set the value of my slider (so that it scrolls smoothly) and call debounce on the function inside that does the http calls.

<Slider
          onChange={handleChange}
          valueLabelFormat={valueLabelFormat}
          //   aria-labelledby="discrete-slider"
          aria-labelledby="non-linear-slider"
          valueLabelDisplay="auto"
          value={value}
          min={1}
          max={5}
          step={0.05}
        />

This is how my handleChange() looks like

const boostedLabel ="some label"

const handleChange = (_event, newValue) => {
    //Update value back to Slider
    setValue(newValue);

    const debouncedFunc = () => updateWeightValue(boostedLabel, newValue);
    debounce(debouncedFunc, 300, {leading:false, trailing:true});
  };

However this does not work! The slider moves but it does not seem like the debouncedFunc is triggered at all.

How do I sort of buffer my requests?

Currently I am using Material UI's slider in my React project. I am using the onChange property to trigger a http post requests when I move along the slider. The code looks something like this.

<Slider
          onChange={handleChange}
          valueLabelFormat={valueLabelFormat}
          //   aria-labelledby="discrete-slider"
          aria-labelledby="non-linear-slider"
          valueLabelDisplay="auto"
          value={value}
          min={1}
          max={5}
          step={0.05}
        />

However, if you can imagine, the onChange will be too frequent and there will be too many HTTP requests being sent. This is definitely not remended. I am also not intending to use onChangeCommitted as it is not my desired way of interaction.

So this is what I want to do:
IF I move along the slider (or onChange), onChange will continually update the value, but http requests will only be triggered every 500ms (with the latest value updated with onChange) . Rather than triggering a request for every onChange!
I have totally no idea how to achieve this..... setInterval or..? Not really sure. Help will be appreciated.
Thank you!


UPDATE
The answer is to use debounce (look at the answer below by k.s.)
However my use case is slightly different from the usual use cases. The function that I am using in my debounce requires inputs to it. I am using debounce inside my handleChange() function instead as I need the handleChange to set the value of my slider (so that it scrolls smoothly) and call debounce on the function inside that does the http calls.

<Slider
          onChange={handleChange}
          valueLabelFormat={valueLabelFormat}
          //   aria-labelledby="discrete-slider"
          aria-labelledby="non-linear-slider"
          valueLabelDisplay="auto"
          value={value}
          min={1}
          max={5}
          step={0.05}
        />

This is how my handleChange() looks like

const boostedLabel ="some label"

const handleChange = (_event, newValue) => {
    //Update value back to Slider
    setValue(newValue);

    const debouncedFunc = () => updateWeightValue(boostedLabel, newValue);
    debounce(debouncedFunc, 300, {leading:false, trailing:true});
  };

However this does not work! The slider moves but it does not seem like the debouncedFunc is triggered at all.

Share Improve this question edited Jun 23, 2020 at 2:02 Daryll asked Jun 22, 2020 at 20:30 Daryll Daryll 5713 gold badges9 silver badges16 bronze badges 2
  • Hey, thanks for posting! Can you please show your current react code, specifically the onChange function. Thanks! – AttemptedMastery Commented Jun 22, 2020 at 20:36
  • You can look into lodash debounce lodash./docs/4.17.15#debounce – SakoBu Commented Jun 22, 2020 at 20:41
Add a ment  | 

2 Answers 2

Reset to default 6

Your use case isn't notably different from other debounce use cases, you just aren't yet fully understanding how debounce should be used. Calling debounce does not invoke your function; instead it returns a debounced version of your function which can then be called as normal with however many parameters you need.

You should not be calling debounce in handleChange. You only want to call it once for the life of the element using it -- then you use the debounced version of the function (returned by debounce) within your handleChange function.

The simplest manner to do this is to create your debounced function at the top-level (entirely outside of your ponent). This isn't pletely safe, however, if it is possible for you to have more than one element on the page for that type of ponent. If you had multiple elements sharing the same debounced function, it would be possible (though unlikely) for a very fast user to change more than one slider before the end of the delay (e.g. within 300 milliseconds) in which case the first element changed would not get saved to the back-end. If the debounced function is actually saving the current state of all the editable elements on the page (and not only the one triggering the change event), then calling debounce at the top-level is what you want.

To avoid the slight dangers of a top-level debounce, you can call debounce in your ponent in a manner that ensures only calling it once for your element. For the 3rd slider in my example below, I'm calling debounce in a lazy state initializer and ignoring the setter that is also returned by useState. Then on each re-render, the same debounced function will be used. With slight changes, it would also be possible to leverage useRef instead of useState or useCallback or useMemo (though the guarantees of only calling debounce once per element are not as safe for useCallback and useMemo -- the documentation states "You may rely on useMemo as a performance optimization, not as a semantic guarantee.").

The example below shows 3 sliders (all sharing the same state). You can see that callHttpRequest just logs its arguments to the console. Each of the 3 sliders calls this in their handleChange functions in a different manner. The first is not debounced at all, so you can see many console logs as you move the slider. The second is debounced at the top-level, so you only see the console log when you stop moving the slider for at least 300 milliseconds. The third is debounced in the ponent within the lazy initializer passed to useState and behaves the same as the second slider.

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Slider from "@material-ui/core/Slider";
import VolumeDown from "@material-ui/icons/VolumeDown";
import VolumeUp from "@material-ui/icons/VolumeUp";
import debounce from "lodash/debounce";

const useStyles = makeStyles({
  root: {
    width: 300
  }
});

const callHttpRequest = (eventSrcDesc, newValue) => {
  console.log({ eventSrcDesc, newValue });
};
const topLevelDebounceCallHttpRequest = debounce(callHttpRequest, 300, {
  leading: false,
  trailing: true
});

export default function ContinuousSlider() {
  const classes = useStyles();
  const [value, setValue] = React.useState(30);

  const handleChangeNoDebounce = (event, newValue) => {
    setValue(newValue);
    callHttpRequest("volume-no-debounce", newValue);
  };
  const handleChangeUsingTopLevelDebounce = (event, newValue) => {
    setValue(newValue);
    topLevelDebounceCallHttpRequest("volume-top-level", newValue);
  };
  const [stateDebounceCallHttpRequest] = React.useState(() =>
    debounce(callHttpRequest, 300, {
      leading: false,
      trailing: true
    })
  );
  const handleChangeUsingStateDebounce = (event, newValue) => {
    setValue(newValue);
    stateDebounceCallHttpRequest("volume-useState", newValue);
  };

  return (
    <div className={classes.root}>
      <Typography id="continuous-slider" gutterBottom>
        Volume (No Debounce)
      </Typography>
      <Grid container spacing={2}>
        <Grid item>
          <VolumeDown />
        </Grid>
        <Grid item xs>
          <Slider
            value={value}
            onChange={handleChangeNoDebounce}
            aria-labelledby="continuous-slider"
          />
        </Grid>
        <Grid item>
          <VolumeUp />
        </Grid>
      </Grid>
      <Typography id="continuous-slider-top-level-debounce" gutterBottom>
        Volume (Debounce called at top-level)
      </Typography>
      <Grid container spacing={2}>
        <Grid item>
          <VolumeDown />
        </Grid>
        <Grid item xs>
          <Slider
            value={value}
            onChange={handleChangeUsingTopLevelDebounce}
            aria-labelledby="continuous-slider-top-level-debounce"
          />
        </Grid>
        <Grid item>
          <VolumeUp />
        </Grid>
      </Grid>
      <Typography id="continuous-slider-useState-debounce" gutterBottom>
        Volume (Debounce called in useState lazy initializer)
      </Typography>
      <Grid container spacing={2}>
        <Grid item>
          <VolumeDown />
        </Grid>
        <Grid item xs>
          <Slider
            value={value}
            onChange={handleChangeUsingStateDebounce}
            aria-labelledby="continuous-slider-useState-debounce"
          />
        </Grid>
        <Grid item>
          <VolumeUp />
        </Grid>
      </Grid>
    </div>
  );
}

Here's a second sandbox that helps demonstrate the difference between the top-level debounce vs. the element-specific debounce: https://codesandbox.io/s/debounce-slider-onchange-jimls?file=/demo.js. In this sandbox, the entire ponent is rendered twice and the delay is 3 seconds instead of 300 milliseconds. This provides plenty of time to move the two different sliders using the top-level debounce before the end of the delay and see that only the second produces a console log; whereas doing the same thing with the two sliders using the element-specific debounced function (managed via useState) produces one console log from each element.

Lodash debounce documentation: https://lodash./docs/4.17.15#debounce

As mentioned in the ment. This is a perfect job for a debounce function. There are plenty of variations out there. Here's an example using lodash/debounce function onChange={debounce(handleChange)}, it returns a function, which is good because the button is expecting a function reference. So on render your handleChange is called as the debounce function parameter and then you invoke it on the actual change event, and no matter how many handleChange events you will provide - only the last one will run.

Here's one of the implementations of a debounce function under the hood:

function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

taken from here if you want to read a bit more about how it works.

UPDATE

The debounce function, returns a function, which you need to invoke.

const boostedLabel = "foo"

const handleChange = (_event, newValue) => {
  setValue(newValue);
  updateWeightValue(boostedLabel, newValue)
}

and still wrap with debounce when passing to the onChange prop, it will have all the arguments that are being passed to the handleChange

<Slider
          onChange={debounce(handleChange)}
          valueLabelFormat={valueLabelFormat}
          //   aria-labelledby="discrete-slider"
          aria-labelledby="non-linear-slider"
          valueLabelDisplay="auto"
          value={value}
          min={1}
          max={5}
          step={0.05}
        />

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论