Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debounce and onChange #1360

Closed
Aetet opened this issue Apr 6, 2014 · 14 comments
Closed

Debounce and onChange #1360

Aetet opened this issue Apr 6, 2014 · 14 comments

Comments

@Aetet
Copy link

Aetet commented Apr 6, 2014

Hello. I have some problem about text input and onChange event. I need debouncing user input, but if I debounce onChange handle, there's no e.target and no opportunity for extract input value. How can I hack this situation?

Example: http://jsfiddle.net/e6JQn/

@syranide
Copy link
Contributor

syranide commented Apr 6, 2014

You have a bigger issue that you can't workaround in that scenario, not updating the value of the input right away will cause React to actively revert to the old value (as if it was read-only), thus when you later update the value the cursor will reset and always be at the end.

Why do you need to debounce onChange? Someone has to be typing super-sonic for that to become an issue, also, React takes care of that case anyway (unless your onChange-handler is doing something ridiculously expensive).

@Aetet
Copy link
Author

Aetet commented Apr 8, 2014

solution: http://jsfiddle.net/Aetet/a6RUP/

@iandunn
Copy link

iandunn commented Oct 3, 2017

UPDATE: This is all wrong :) See #1360 (comment)


not updating the value of the input right away will cause React to actively revert to the old value

That's what I would have thought too, but that doesn't actually seem to be the case.

Why do you need to debounce onChange? Someone has to be typing super-sonic for that to become an issue

It seems like a common thing to me. For example, re-rendering a preview of an embedded video when changing the URL:

Design mockup for previewing an embedded URL

Granted, in that specific scenario, most people will copy/paste rather than typing, but it would be pretty awful performance for anyone who manually typed it, or who wanted to trim out some extra parts that were accidentally copied, etc.

It's also important to keep in mind that a huge segment of people around the world aren't working on powerful computers like many developers are. People shouldn't have to be rich to have a smooth experience.

also, React takes care of that case anyway

Can you elaborate on that?

@gaearon
Copy link
Collaborator

gaearon commented Oct 3, 2017

@iandunn

In your scenario you wouldn't need to debounce onChange related to the text field. You still want to update the text field synchronously in response to user input. You'd want to debounce some extra work that is triggered by changes.

For example your code could look like

constructor(props) {
  super(props);
  this.state = {
    inputText: '',
    currentUrl: ''
  };

  this.handleChange = this.handleChange.bind(this);
  this.updateUrl = this.updateUrl.bind(this);

  // Debounce
  this.updateUrl = _.debounce(this.updateUrl, 500);
}

handleChange(e) {
  this.setState({
    inputText: e.target.value
  });

  // It's debounced!
  this.updateUrl();
}

updateUrl() {
  this.setState({
    currentUrl: this.state.inputText
  });
}

render() {
  return (
    <div>
      <input value={this.state.inputText} />
      <MyVideo url={this.state.currentUrl} />
    </div>
  );
}

This way the text updates happen synchronously as expected, but the video is updated when necessary.

@iandunn
Copy link

iandunn commented Oct 4, 2017

Ah, yeah, I was conflating debouncing the input element's value and debouncing the update of the video preview.

And I also mistakenly thought that the input field I was working with was controlled when it's actually uncontrolled. ( It's wrapped in a component, so I didn't notice until I opened that and looked at it ). That's why I was thinking that debouncing a controlled field wouldn't interfere with its value being updated.

My bad, thanks for the reply :)

@OEvgeny
Copy link

OEvgeny commented Nov 30, 2017

@gaearon Cool, but you code tries to change state after component unmounted 😄

@OEvgeny
Copy link

OEvgeny commented Nov 30, 2017

Found a good solution for that:

constructor(props) {
  super(props);
  this.state = {
    inputText: '',
    currentUrl: ''
  };

  this.handleChange = this.handleChange.bind(this);
  this.updateUrl = this.updateUrl.bind(this);

  // Debounce
  this.updateUrl = _.debounce(this.updateUrl, 500);
}

...

componentWillUnmount() {
  this.updateUrl.cancel(); // or flush() in case we need to fire handler immediately.
}

@quantuminformation
Copy link

What about if the input is connected to a redux action and its value to the store. Is this a good solution:

https://medium.com/@justintulk/debouncing-reacts-controlled-textareas-w-redux-lodash-4383084ca090

@lukenofurther
Copy link

If anyone's looking for a decent solution to this then react-debounce-input works very nicely. It debounces the onChange event so your handler isn't triggered at all until data entry has stopped for the timeout period. I've only tried this out for text inputs so far.

@nealYangVic
Copy link

@gaearon this is very good solution, but in this case, we setState will render the component twice inside the same function?

@joebourne
Copy link

Is there a chance this will run into problems with the setState queue?

@shyarora
Copy link

Debounce with Hooks

import React, { useState, useCallback } from "react";

const debounce = (fn, delay) => {
  let timeoutId;
  return function(...args) {
    clearInterval(timeoutId);
    timeoutId = setTimeout(() => fn.apply(this, args), delay);
  };
};

function SomeComponent() {
  const [input, setInput] = useState("");
  const [videoUrl, setVideoUrl] = useState("");

  const debounceCallback = useCallback(
    debounce(value => {
      setVideoUrl(value);
    }, 1000),
    []
  );

  const onInputChangeHandler = ({ target: { value } }) => {
    setInput(value);
    debounceCallback(value);
  };

  return (
    <div>
      <input value={input} onChange={onInputChangeHandler} />
      <p>{videoUrl}</p>
    </div>
  );
}

export default SomeComponent;

@mburnett8
Copy link

Extend useState hook

import { useState } from "react";
import _ from "underscore"
export const useDebouncedState = (initialState, durationInMs = 500) => {
	const [internalState, setInternalState] = useState(initialState);
	const debouncedFunction = _.debounce(setInternalState, durationInMs);
	return [internalState, debouncedFunction];
};
export default useDebouncedState;

Use hook

import useDebouncedState from "../hooks/useDebouncedState"
//...
const [usernameFilter, setUsernameFilter] = useDebouncedState("")
//...
<input id="username" type="text" onChange={e => setUsernameFilter(e.target.value)}></input>

https://trippingoncode.com/react-debounce-hook/

@endrureza
Copy link

@shyam-arora thank you, your solution works like a charm !!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests