I have a range input field with a numeric value which changes when I drag:
When I drag all the way to the right, the max value of the input increases. Then, when I release (onMouseUp or onTouchEnd), the max value decreases so that I can drag further and continue increasing max by dragging:
When I drag all the way to the left, the min value of the input decreases. Then, when I release (onMouseUp or onTouchEnd), the min value increases so that I can drag further and continue decreasing min by dragging:
I should always have a range of 99. For example, if I have increased the max value to 530, the min value will be 431.
PROBLEM:
I have two recursive setTimeout
s set for the min
and max
values changing.
When the user first drags all the way to either side, the number should slowly change. If they hold it for 2 seconds, the number should increase more quickly. 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 the first time. But subsequent times, it latches onto the faster timeout first:
This is despite my clearing of timeouts when the drag ends:
clearTimeout(this.increaseStakeLimits);
clearTimeout(this.decreaseStakeLimits);
clearTimeout(this.timer);
Why is the first (slower) timeout not engaging?
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'))
That's a really clever UI!
increaseStakeLimits()
fires continuously the whole time the user holds the slider to the far right, so you're continually setting new setTimeouts to change this.stakeChangeTimeout
to the shorter interval after two seconds (and continually setting new values for this.fasterChangeStake
). These continue to fire even after handleRelease()
tries to reset the interval to the longer value, which forces it back into fast mode until all of them have fired (the clearTimeout
in handleRelease()
only catches one of them.)
You can fix this by only setting that timeout once:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With