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;
Use the set interval method inside the function to change the state after a fixed amount of time. setInterval method takes two parameter callback and time. The callback function is called again and again after that given amount of time. Use the setState method to change the state of the component.
Solution. To avoid that problem, we better execute a function in proper timing which means after a user stops typing for a while. And setTimeout helps us to do that. The setTimeout() method calls a function or evaluates an expression after a specified number of milliseconds.
To keep the string the user is typing, use the useState hook to store the text the user is typing. Then give that state to the value of the input. Also be sure to use setState on the onChange event handler of the input, otherwise the input value won't change.
You don't really need a ref to solve this. A call touseEffect
should be enough:
useEffect(() => {
const timeout = setTimeout(() => {
setOutput(userValue);
}, 600);
return () => {
clearTimeout(timeout);
};
}, [userValue]);
const updateOutput = (e) => {
setUserValue(e.target.value);
};
You can try this way
const updateOutput = () => {
setUserValue(inputRef.current.value);
setTimeout(() => {setOutput(inputRef.current.value)}, 700) }
Hope this will help you.
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