Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make React input onChange set state only after onChange stops firing for set time?

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.

enter image description here

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;


like image 252
NicoWheat Avatar asked Dec 04 '19 06:12

NicoWheat


People also ask

How do you change state continuously after a certain amount of time in React?

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.

How do you execute a function only after the user stops typing React?

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.

How do you delay onChange in React?

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.


2 Answers

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);
};
like image 174
Damien Flury Avatar answered Oct 16 '22 18:10

Damien Flury


You can try this way

const updateOutput = () => {
setUserValue(inputRef.current.value);
setTimeout(() => {setOutput(inputRef.current.value)}, 700) }

Hope this will help you.

like image 33
Armen Khachatryan Avatar answered Oct 16 '22 19:10

Armen Khachatryan