Recursive `setTimeout` is not involved in the expected path

I have a range input field with a numeric value that changes when dragging:

enter image description here

When I drag all the way to the right, the maximum input value increases. Then, when I release (onMouseUp or onTouchEnd), the maximum value decreases, so I can drag further and continue to increase max by dragging:

enter image description here

When I drag all the way to the left, the minimum input value decreases. Then, when I release (onMouseUp or onTouchEnd), the min value increases, so I can drag further and continue to decrease min by dragging:

enter image description here

I should always have a range of 99. For example, if I increased the maximum value to 530, the minimum value would be 431.

Problem:

I have two recursive setTimeout for changing the values ​​of min and max .

When the user first drags all the way in any direction, this number should slowly change. If they hold it for 2 seconds, the number should increase faster. Relevant Code:

 // After arbitrary period, increase the rate at which the max value increments this.fasterChangeStake = setTimeout(() => { this.stakeChangeTimeout = this.FAST_STAKE_CHANGE_TIMEOUT; }, 2000); 

This works for the first time. But in subsequent moments, he first lingers on a faster timeout:

enter image description here

This is despite the fact that I ended the timeouts when the drag and drop ended:

 clearTimeout(this.increaseStakeLimits); clearTimeout(this.decreaseStakeLimits); clearTimeout(this.timer); 

Why is the first (slower) timeout not involved?

Codepen: https://codepen.io/alanbuchanan/pen/NgjKMa?editors=0010

JS:

 const {observable, action} = mobx const {observer} = mobxReact const {Component} = React @observer class InputRange extends Component { constructor() { super(); this.INITIAL_STAKE_CHANGE_TIMEOUT = 200; this.FAST_STAKE_CHANGE_TIMEOUT = 20; this.SNAP_PERCENT = 10; this.increaseStakeLimits = this.increaseStakeLimits.bind(this); this.decreaseStakeLimits = this.decreaseStakeLimits.bind(this); } @observable min = 0; @observable max = 99; @observable stakeChangeTimeout = this.INITIAL_STAKE_CHANGE_TIMEOUT; @observable isIncreasing = false; @observable isDecreasing = false; @observable stake = 0; @action updateStake = (amount) => { if (amount > -1) { this.stake = amount } }; increaseStakeLimits() { const { updateStake } = this; this.max = this.max += 1; this.min = this.max - 99 updateStake(this.max); // After arbitrary period, increase the rate at which the max value increments this.fasterChangeStake = setTimeout(() => { this.stakeChangeTimeout = this.FAST_STAKE_CHANGE_TIMEOUT; }, 2000); // Recursive call, like setInterval this.timer = setTimeout(this.increaseStakeLimits, this.stakeChangeTimeout); this.isIncreasing = true; } decreaseStakeLimits() { console.warn('this.stake:', this.stake) const { stake } = this const { updateStake } = this; this.min = this.min -= 1; this.max = this.min + 99 updateStake(this.min); // After arbitrary period, increase the rate at which the max value increments this.fasterChangeStake = setTimeout(() => { this.stakeChangeTimeout = this.FAST_STAKE_CHANGE_TIMEOUT; }, 2000); // Recursive call, like setInterval this.timer = setTimeout(this.decreaseStakeLimits, this.stakeChangeTimeout); this.isDecreasing = true; } handleStakeChange = e => { clearTimeout(this.increaseStakeLimits); clearTimeout(this.decreaseStakeLimits); clearTimeout(this.timer); const { updateStake } = this; const { stake } = this; const val = Number(e.target.value) // User has scrolled all the way to the right if (val >= this.max) { console.warn("scrolled to right") this.increaseStakeLimits(); // User has scrolled all the way to the left } else if (val <= this.min) { console.warn("scrolled to left") if (val > -1) { this.decreaseStakeLimits(); } } else { updateStake(val); } }; handleRelease = () => { console.warn("RANGE:", this.max - this.min) console.warn("released"); clearTimeout(this.fasterChangeStake); clearTimeout(this.timer); // Reset the timeout value to the initial one this.stakeChangeTimeout = this.INITIAL_STAKE_CHANGE_TIMEOUT; this.SNAP_PERCENT = 10 const snapAmount = this.SNAP_PERCENT if (this.isIncreasing) { this.max += snapAmount } if(this.isDecreasing && this.min > 0) { this.min -= snapAmount } this.isIncreasing = false; this.isDecreasing = false; }; render() { const { stake } = this; const style = { backgroundSize: (stake - this.min) * 100 / (this.max - this.min) + "% 100%" }; return ( <div className="rangeContainer"> <div>{this.stake}</div> <div className="inputContainer"> <input id="betRangeId" type="range" min={this.min} max={this.max} step="1" ref={input => { this.textInput = input; }} value={this.stake} onChange={this.handleStakeChange} onTouchEnd={this.handleRelease} onMouseUp={this.handleRelease} style={style} /> </div> </div> ); } } ReactDOM.render(<InputRange />, document.getElementById('root')) 
+5
source share
1 answer

This is a really smart user interface!

increaseStakeLimits() continuously fires all the time when the user holds the slider in the far right corner, so you constantly set new setTimeouts parameters to change this.stakeChangeTimeout to a shorter interval after two seconds (and constantly set new values ​​for this.fasterChangeStake ). They continue to fire even after handleRelease() tries to reset the interval to a longer value, which forces it to return to fast mode until all of them are started ( clearTimeout in handleRelease() only catches one of them) .

You can fix this by setting only one timeout:

 if (!this.fasterChangeStake) { this.fasterChangeStake = setTimeout(() => { this.stakeChangeTimeout = this.FAST_STAKE_CHANGE_TIMEOUT; this.fasterChangeStake = false; // <-- also do this in handleRelease() after clearing the timeout }, 2000); } 

https://codepen.io/anon/pen/OgmMNq?editors=0010

+2
source

Source: https://habr.com/ru/post/1269027/


All Articles