Pawel Stadnicki
Pawel Stadnicki

Reputation: 403

How to debounce user input in reactjs using rxjs

My problem may be a trivial one but I wasn't able to find the answer so far.

How can I defer (debounce) updating state in React while user is typing, to avoid unnecessary updates?

Having <input onChange={this.onChange} .../>, how can I bind onChange event with rxjs? Should I try to make this input observable or should I use FromEventPattern?

In both cases I have no idea how to bind React events with rxjs. The second question is whether the user will see any input changes during debounce?

Upvotes: 15

Views: 10238

Answers (4)

marcelocra
marcelocra

Reputation: 2513

I keep getting to this question, while I prefer this solution from another one.

Copied below for simplicity. Please upvote the original.


You will need to cretae observable from change events(for example using Subject) and then debounce on that.

Here is the fully featured example for you:

class Search extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      search: '',
      debounced: '',
    };
    this.onSearch$ = new Rx.Subject();
    this.onSearch = this.onSearch.bind(this);
  }
  componentDidMount(){
    this.subscription = this.onSearch$
      .debounceTime(300)
      .subscribe(debounced => this.setState({ debounced }));
  }
  
  componentWillUnmount() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
  
  onSearch(e) {
    const search = e.target.value;
    this.setState({ search });
    this.onSearch$.next(search);
  }

  render() {
    const { search, debounced } = this.state;
    return (
      <div>
        <input type="text" value={search} onChange={this.onSearch} />
        <div>debounced value: {debounced}</div>
      </div>
    );
  }
}

ReactDOM.render(
  <Search />,
  document.getElementById('root')
);
<script src="https://unpkg.com/[email protected]/bundles/Rx.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Upvotes: 0

andy.bin
andy.bin

Reputation: 21

try like this:

class MyScene extends React.Component {

    constructor(props) {
        var onChangeTextObservable = Rx.Observable.fromEventPattern(
           (handler) => {this.onChangeText = handler}
        );
        onChangeTextObservable.subscribe(x => console.log(x));
    }

    render() {
       return (
          <TextInput onChangeText={this.onChangeText}>
       )
    }

}

Upvotes: 2

Pawel Stadnicki
Pawel Stadnicki

Reputation: 403

based on omerts propositions, (especially solution #1) here is my final code

input: Rx.Subject<any>;

constuctor(...){
   this.input = new Rx.Subject();
   this.input.debounce(1000).subscribe(this.processInput);
}

handleChange = event => {
   event.persist();
   this.input.onNext(event);

    };

processInput = x => {
   // invoke redux/flux action to update the state
}

render(){
   ...
   <input onChange={this.handleChange} ... />
   ...
}

Upvotes: 9

omerts
omerts

Reputation: 8848

Solution #1

Using subjects:Fiddle

const state = new Rx.Subject()
              .debounceTime(1000)
              .scan((acc) => {
                return ++acc
              }, 0).do(::console.log)


const handler = (e) => {        
  state.next(e)
}

state.startWith(0).subscribe((clicks) => {
  ReactDOM.render(<button onClick={handler}>Clicked {clicks}</button>, document.querySelector('#app')) 
})

Solution #2

Using rxjs's fromEvent: Fiddle

// Intial render so element exists in dom (there is probably a better pattern)
ReactDOM.render( <button id='clickMe'>Click Me</button>, document.querySelector('#app')) 

const clicks = Rx.Observable
                .fromEvent(document.getElementById('clickMe'), 'click')
                .do(::console.log)
                .debounceTime(1000)
                .scan((acc) => {
                  return ++acc
                }, 0)

clicks.subscribe((clicks) => {
  ReactDOM.render( <button id='clickMe'>Click Me {clicks}</button>, document.querySelector('#app')) 
})

Solution #3

Note: highly experimental, and just something I tried to do for fun.

This is more for an action based architecture, where you have actions that change your state (flux). This is a handler that is fully standalone. It is used with a custom operator 'fromEventArgs': Fiddle (look at the console)

const handler = (e) => {        
  Rx.Observable
    .fromEventArgs(e, 'UniqueKey') 
    .debounceTime(1000)        
    .subscribe(x => console.log('Send an action', x))
}

Upvotes: 9

Related Questions