Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rxjs debounce on react text input component

Tags:

reactjs

rxjs

I have the following react component

<input className={styles.incSrchTextBox} type="text" name="search" placeholder="Search.."
   onChange={this.onChange} />


onChange(e) {
    const newText = e.target.value;
    console.log(newText);
    this.setState({ searchText: newText });
}

How do I use debounce on rxjs on this?

like image 344
tmp dev Avatar asked Jun 01 '17 06:06

tmp dev


3 Answers

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>
like image 160
Oles Savluk Avatar answered Nov 03 '22 12:11

Oles Savluk


This would be a good use case for Refract!

The first step would be to pull the input out into a separate component:

const Input = ({ onChange, value }) => (
    <input type="text" value={value} onChange={onChange} />
)

Next step would be to wrap this component with Refract's withEffects higher-order component, with a handler and an aperture to handle the side-effects like this:

import { withEffects } from 'refract-rxjs'
import { debounceTime } from 'rxjs/operators'

const Input = ({ onChange, value }) => (
    <input type="text" value={value} onChange={onChange} />
)

const aperture = () => component =>
    component.observe('value').pipe(debounceTime(300))

const handler = ({ onUpdate }) => value => onUpdate(value)

const DebouncedInput = withEffects(handler)(aperture)(Input)

An aperture lets you observe your component's props. In this case, it would make sense to observe the value prop - every time the value changes, the component.observe('value') stream gets a new value.

The handler is a function called with each value output by the aperture's stream. In this case, the debounced value is passed straight through to a new prop called onUpdate.

Both apertures and handlers are explained in detail in the docs - Observing React introduces apertures, and Handling Effects explains handlers.

As an example of how you would use this:

class Search extends React.Component {
    state = { debounced: '', search: '' }

    onSearch = e => this.setState({ search: e.target.value })
    onUpdate = debounced => this.setState({ debounced })

    render() {
        return (
            <div>
                <DebouncedInput
                    type="text"
                    value={this.state.search}
                    onChange={this.onSearch}
                    onUpdate={this.onUpdate}
                />
                <div>debounced value: {debounced}</div>
            </div>
        )
    }
}

With this code, the text DebouncedInput would display the user's input instantly (which is ideal for UX), while debouncing the side-effect of calling the onUpdate callback. It would then be trivial to expose this onUpdate to components which consume the Search component!

like image 2
Joe McGrath Avatar answered Nov 03 '22 11:11

Joe McGrath


I agree with the example by Oles Savluk. In addition, I would extract the Subject logic out of the component. It doesn't need to live inside the component, as it has no state, and I think this also makes the component easier to understand.

Also: The example is updated to use RxJS 6.2.2

const { Subject } = rxjs;
const { debounceTime } = rxjs.operators;

const onSearch$ = new rxjs.Subject().pipe(
    debounceTime(300)
);

class Search extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = {
      search: '',
      debounced: '',
    };
  }

  componentDidMount(){
    this.subscription = onSearch$.subscribe(
        debounced => this.setState({ debounced })
    );
  }
  
  componentWillUnmount() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
  
  onSearch = (e) => {
    const search = e.target.value;
    this.setState({ search });
    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/rxjs.umd.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>
like image 1
skovmand Avatar answered Nov 03 '22 11:11

skovmand