nkbt / react-debounce-input

React component that renders Input with debounced onChange

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

getting a lock

vgoklani opened this issue · comments

Hi,
I'm using react-infinite to render a column of data, and I use react-debounce-input for filtering the data. This works well most of the time. However, when a new piece of data comes through the pipeline, it gets automatically appended to the top of the list (via setState). Unfortunately what happens is that if the user is typing in a query while a piece of data is coming in, the search box gets scrambled (setState from the data column confuses the search query). Basically I need to add a LOCK such that if the user is in the middle of entering a query, the data column should not update. The problem is that I don't know how to get the lock from react-debounce-input? Any ideas?

I can control the setState from shouldComponentUpdate for react-infinite, but I'm not sure how to intercept the search box lock.

While updating you can:

  1. Replace ReactDebounceInput with just a normal input, React will do the dirty job of removing anything around it and will keep the input, so user should not lose his input state
  2. Set debounceTimeout to -1 so it will not fire up updates. When your stuff is updated - revert it back to whatever value you had there previously

Those are two solutions I can quickly come up with. Let me know if it works out for you

Cheers,
Nik

I think I have the same problem. I use DebounceInput as a Searchfilter on a queryset.
E.g. the api URL = /api/2.0/nodes?q=searchword
Based on the json repsonse of that API it will display a table of results.
Because I don't want to do an API request on every key down event in the searchbox, I use DebounceInput.

However, sometimes the value of the input is not correct.
It's easiest to replicate by placing 1 letter in the DebounceInput, hitting backspace and quickly typing a new letter. This will result in no letters at all in the box.

<DebounceInput
    type="search"
    minLength={2}
    debounceTimeout={300}
    value={this.props.q}  // from state from a Store
    onChange={this.props.onChangeHandler}
    id="id_q"
>

Now this can be "solved" by removing the value attribute from the DebounceInput.
However, if I do that, then I'm unable to clear the value of DebounceInput with a onClick event of another button. I tried with jquery: $('#id_q').val('');

I'm thinking, because DebounceInput is a controlled component (https://facebook.github.io/react/docs/forms.html).

Is this even possible?

placing 1 letter in the DebounceInput, hitting backspace and quickly typing a new letter. This will result in no letters at all in the box.

That sounds like a bug (though a different one to the topic)

I tried with jquery: $('#id_q').val('');

Do not modify DOM with any other tools other then React if you don't want to have completely unpredictable effects

DebounceInput is a controlled component

It can be both =) If you don't specify value it won't be controllable. Try to use defaultValue instead (usual one for any uncontrollable input in React)

Your use-case seems like an actually the main use-case for DebounceInput =). And the way we use it on our projects. So I am quite surprised it does not work as expected.

Would be great to have a Codepen example to play with, so we can find what could be the issue

Thank you for the quick reaction.
About the 1 letter in the box "bug"; this was overcome by placing minLength=0
You can reproduce it here:
http://nkbt.github.io/react-debounce-input/example/
and then slide Min Length to 2 or higher
type something in the first text field. Select all text in the first text field, and replace it with 1 character. The value is now blank/undefined. I'm not sure if this is a bug or a feature.

maybe it's because I use the DebounceInput like so:

----- SearchBox.jsx -------

import React from 'react';
import DebounceInput from 'react-debounce-input';

export default class SearchBox extends React.Component {
    render() {
      return (<DebounceInput
        type="search"
        minLength={0}
        debounceTimeout={300}
        defaultValue={this.props.q} // When I use defaultValue: No problem with scrabling my input, but now impossible to reset
        value={this.props.q} // When I use value: Problem with scrabling, but now possible to reset
        tabIndex={this.props.tabIndex ? this.props.tabIndex:"1"}
        placeholder={this.props.placeholder ? this.props.placeholder:"Search"}
        onChange={this.props.onChangeHandler}
        autoComplete={this.props.autoComplete ? this.props.autoComplete:"off"}
        name={this.props.name ? this.props.name : "q"}
        className={this.props.className ? this.props.className:"form-control"}
        id={"id_" + this.props.name ? this.props.name : "q"}/>);
    }
}

I can imagine there is a better way to extend DebounceInput.

Also, if you are doing wrapping, then look at https://github.com/nkbt/react-text-filter (it is basically wrapped DebounceInput)

I remember having some problems with it similar to what you are trying to explain. See examples there and the code of TextInput how it keeps behaviour exactly the same to what you want.

Double-checked the code of Filter and there seems like no issues wrapping Debounce. Everything is simple. I had problems initially but then fixed DebounceInput to allow easy extending.

So just look at it, maybe there is some tiny little thing that I do not notice in your code that makes things awkward...

ahhh I think I know it.
It's because the searchbox component I use is INSIDE the resultset.

So, I have a

component.
This has a component
In this TableFooter, there are searchboxes for each column.
Entering text in one searchbox alters the resultset on Table, which probably also triggers a new draw on the TableFooter and hence the searchbox, overwriting the text the user was typing with the search word.

For me to get it to work, is to move the search filters outside of the table which holds the result. I'm going to try this now.

Oh cool! Let me know how it goes

Separating the components didn't seem to matter in my code, because (I suspect) they still use the same store (flux), and on updating of the store all components were updated.
E.g. the search string is changed in the store, then after an ajax request is done to get the new resultset, after that is done, it will update the store's state again.

So I think I have to read a bit more into flux and probably create more Stores or don't store the search value into the store but in the component itself.

I am seeing a problem in a filter text box and typing relatively quickly. My on change function updates my redux store with the new value, but if I type a bit more quickly, the new characters tend to disappear.

Tellingly, it improves the lower I set the debounceTimeout value. When I set it to 1, the glitch disappears entirely.

Have you seen anything like that, or have any advice?

@twmills this is how it works (or the reason it does not work as you expect it to work) in your case:

  1. type smth - abc in the input
  2. onChange invoked after debounce timeout - abc event.target.value
  3. store it in redux store - abc value in store
  4. meanwhile you keep typing - abcdef in the input
  5. store change subscriber reacts and sets value from redux store - back to abc
  6. ced are "lost"

It is completely expected behaviour when you have delayed input. If you want to keep it in redux store and have "controllable" input with instant updates, then why do you need debounced-input in the first place? What is the desired behaviour for you? I can most likely help you to design it

PS: if you want to set value of input when user opens the page from the last input - use defaultValue, not value

I have a similar issue in different use case. My use case is DebouncedInput onChange will trigger AJAX to search data. The data received includes the search text. There are also other components on the page that would trigger the same AJAX call, but with different search text. The DebouncedInput has to reflect the search text when it receives the data.

The AJAX might finish when the user is changing the DebouncedInput, causing the user's change to reset.

To solve the problem, I've wrapped DebouncedInput in a component to ignore the search text if it was the same as what onChange was triggered with

const React = require("react");

const SearchBox = React.createClass({
    propTypes: {
        value: React.PropTypes.string.isRequired,
        onChange: React.PropTypes.func.isRequired
    },
    getInitialState() {
        return { latestValue: this.props.value };
    },
    componentWillReceiveProps(nextProps) {
        const self = this;
        // Value we receive isn't what we set.
        if (self.state.latestValue !== nextProps.value) {
            self.setState({ latestValue: nextProps.value });
        }
    },
    shouldComponentUpdate(nextProps, nextState) {
        return this.state.latestValue !== nextState.latestValue;
    },
    onChange (e) {
        const self = this;
        self.setState({ latestValue: e.target.value });
        self.props.onChange(e);
    },
    render () {
        const self = this;
        return (
            <div className={"form-inline " + self.props.className}>
                <label>Search {}
                    <DebounceInput className="form-control" minLength={0} debounceTimeout={300} value={self.state.latestValue} onChange={self.onChange} />
                </label>
            </div>
        );
    }
});

module.exports = SearchBox;

@nkbt the scenario you described in your latest comment is exactly what I've run into.

If you want to keep it in redux store and have "controllable" input with instant updates, then why do you need debounced-input in the first place? What is the desired behaviour for you?

In my application, I'm using redux and I expect the input's visible value to reflect the app (redux) state.
I want to keep the server updated with the values that the user enters. But I'd like to avoid sending a request for each change. That's why I'm debouncing - to update the server once the user has stopped (or paused) typing.

Here's a possible API that I came up with:

<DebounceInput 
    debounceTimeout={300} 
    value={this.props.value} 
    onChange={this.handleDebouncedChange} // Asynchronously update the server
    onChangeImmediate={this.handleImmediateChange } // Update the app state
/>

What do you think?
I'd hate to have to go and implement my own debouncing logic when you've already done such a fine job here.
Frankly, I don't see how the component could be used in a flux-style architecture as-is, but maybe I'm missing something.

UPDATE: Edited the API to be backwards-compatible

Try to use defaultValue instead (usual one for any uncontrollable input in React)

I tried to use defaultValue with no value, but that doesn't work. If there's no value prop, then the initial state is set to an empty string. So the defaultValue is never rendered in the input:

getInitialState() {
    return {
      value: this.props.value || ''
    };
  }

Source: https://github.com/nkbt/react-debounce-input/blob/master/src/Component.js#L38