Async Increment at once in a React Counter using hooks
Solution 1:
To do that, wait for the previous change to finish before you start the next one. For instance, one way to do that is with a promise chain; see comments:
// Promise-ified version of setTimeout
const timeout = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const UseStateCounter = () => {
const [value, setValue] = useState(0);
// Remember the promise in a ref we initialize
// with a fulfilled promise
const changeRef = useRef(Promise.resolve());
/* Alternatively, if there's a lot of initialization logic
or object construction, you might use `null` above
and then:
if (!changeRef.current) {
changeRef.current = Promise.resolve();
}
*/
const reset = () => {
queueValueUpdate(0, false);
};
// A function to do the queued update
const queueValueUpdate = (change, isDelta = true) => {
changeRef.current = changeRef.current
// Wait for the previous one to complete, then
.then(() => timeout(4000)) // Add a 4s delay
// Then do the update
.then(() => setValue(prevValue => isDelta ? prevValue + change : change));
};
const asyncIncrease = () => {
queueValueUpdate(1);
};
const asyncDecrease = () => {
queueValueUpdate(-1);
};
// Sadly, Stack Snippets can't handle the <>...</> form
return <React.Fragment>
<section style={{ margin: '4rem 0' }}>
<h3>Counter</h3>
<h2>{value}</h2>
<button className='btn' onClick={asyncDecrease}>Async Decrease</button>
<button className='btn' onClick={reset}>Reset</button>
<button className='btn' onClick={asyncIncrease}>Async Increase</button>
</section>
</React.Fragment>;
};
export default UseStateCounter;
Live Example:
const {useState, useRef} = React;
// Promise-ified version of setTimeout
const timeout = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const UseStateCounter = () => {
const [value, setValue] = useState(0);
// Remember the promise in a ref we initialize
// with a fulfilled promise
const changeRef = useRef(Promise.resolve());
/* Alternatively, if there's a lot of initialization logic
or object construction, you might use `null` above
and then:
if (!changeRef.current) {
changeRef.current = Promise.resolve();
}
*/
const reset = () => {
queueValueUpdate(0, false);
};
// A function to do the queued update
const queueValueUpdate = (change, isDelta = true) => {
changeRef.current = changeRef.current
// Wait for the previous one to complete, then
.then(() => timeout(4000)) // Add a 4s delay
// Then do the update
.then(() => setValue(prevValue => isDelta ? prevValue + change : change));
};
const asyncIncrease = () => {
queueValueUpdate(1);
};
const asyncDecrease = () => {
queueValueUpdate(-1);
};
// Sadly, Stack Snippets can't handle the <>...</> form
return <React.Fragment>
<section style={{ margin: '4rem 0' }}>
<h3>Counter</h3>
<h2>{value}</h2>
<button className='btn' onClick={asyncDecrease}>Async Decrease</button>
<button className='btn' onClick={reset}>Reset</button>
<button className='btn' onClick={asyncIncrease}>Async Increase</button>
</section>
</React.Fragment>;
};
ReactDOM.render(<UseStateCounter />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Note: Normally I make a big noise about handling promise rejections, but none of the promise stuff above will ever reject, so I'm comfortable not bothering with catch
in queueValueUpdate
.