Intro I have a user input element in a create-react-app app. It lets the user type a number, or use arrows to increment the number up or down. The output then displays this number.
Problem The user wants to arrive at a certain number, and no matter how they enter in the number, there are spurious intermediate values that trigger the onChange eventlistener. If the user tries to enter "15" with the keyboard, the output is first "1" and then "15", but I want it to just be "15". I do not want to use a button/submit/etc or other events. If the user presses and holds down the up-arrow from 0 to 500, I want to update directly to 500, after the 'fast-changing' inputs have slowed/stopped.
Goal I want to program an onChange function so that the output is only updated when the onChange has not fired an event for at least 600 ms since the last event.
Details If the user types '1' and then '5' ( for '15' ) fewer than 600ms apart, the output should update to '15', and skip '1'. But, if the user takes longer than 600ms between typing '1' and '5', then the output should first be '1' and then '5'. Likewise, if the user is pressing the increment arrows very fast up and down, the output should not update until they stop or slow down.
in the terminal, make a react app using create-react-app
create-react-app delay-output-app
Code copy and paste this into delay-output-app/src/App.js, replacing all previous code.
import React, { useState, useEffect, useRef } from "react";
import "./App.css";
function App() {
const inputRef = useRef();
const [userValue, setUserValue] = useState(0);
const [output, setOutput] = useState(0);
const updateOutput = () => {
setUserValue(inputRef.current.value);
setOutput(inputRef.current.value);
//if input is changing quickly - do not update output
//only update output if input has not changed for 700ms since last change.
};
return (
<div className="App">
<br></br>
<div>
<h1>Input</h1>
<input
ref={inputRef}
value={userValue}
type="number"
onChange={updateOutput}
></input>
</div>
<br></br>
<div>
<h1>Output</h1>
{output}
</div>
</div>
);
}
export default App;
to run the app in the terminal...
cd delay-output-app
npm start
I have tried playing with setTimeout and setInterval, but I keep running into issues, especially when combined with react useEffect hook. Again, I don't want to use a button or have user need to press anything aside from typing the numbers or clicking arrows to update the output. Only time between input values dictates when an input value is intermediate or should update output.
update to include debounce - I'm under the impression that debounce shouldn't run until the input event is over, but I'm still getting update output after just the wait period, regardless of if the user input event is over.
code with lodash debounce
import React, { useState, useRef } from "react";
import "./App.css";
import _ from "lodash";
function App() {
const inputRef = useRef();
const [userValue, setUserValue] = useState(0);
const [output, setOutput] = useState(0);
const updateOutput = _.debounce(
() => setOutput(inputRef.current.value),
700
);
//if input is changing quickly - do not update output
//only update output if input has not changed for 700ms since last change.
const updateUserValue = () => {
setUserValue(inputRef.current.value);
};
return (
<div className="App">
<br></br>
<div>
<h1>Input</h1>
<input
ref={inputRef}
value={userValue}
type="number"
onChange={() => {
updateOutput();
updateUserValue();
}}
></input>
</div>
<br></br>
<div>
<h1>Output</h1>
{output}
</div>
</div>
);
}
export default App;