Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Lodash debounce in React to prevent requesting data as long as the user is typing

I don't want to fire requests as long as the user is typing. My code should throttle requests so that when the user types quickly, it will fire one request with the latest input value instead of many.

For now when I'm typing "test" it fires 4 different requests:

  1. "t"
  2. "te"
  3. "tes"
  4. "test"

So I found lodash _.debounce and _.throttle ( [https://lodash.com/docs/4.17.4#debounce] ) but don't really understand how I can inplement it to my code. Can anyone help me?

My code:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import './style.css';

import { search } from '../../actions/';

class SearchBar extends Component {
  constructor(props) {
    super(props);
    this.state = { searchTerm: '' };
  }

  startSearch(query) {
    const storedTerms = this.props.searchedTerm;
    let foundDuplicate = false;

    if (storedTerms.length === 0 && query) {
      return this.props.search(query);
    }

    if (storedTerms.length !== 0 && query) {
      const testDuplicate = storedTerms.map(term => term === query);
      foundDuplicate = testDuplicate.some(element => element);
    }

    if (storedTerms.length !== 0 && !query) {
      return false;
    }

    if (foundDuplicate) {
      return false;
    }

  return this.props.search(query);
}

handleInputChange(term) {
  this.setState({ searchTerm: term });
  this.startSearch(term);
}

render() {
  return (
    <div className="Search-bar">
      <input
        value={this.state.searchTerm}
        onChange={event => this.handleInputChange(event.target.value)}
      />
    </div>
  );
}


function mapStateToProps(state) {
  return {
    searchedTerm: state.searchedTerm,
    savedData: state.savedData,
  };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ search }, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);

EDIT:

Thx to Sagiv b.g, I'm adding some explanation:

ok, so the user should type more than 2 letters && also my app should wait minimum 2 seconds before starting ajax request


EDIT2: Thx to Sagiv b.g, for great solution!

I've changed my code like so:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import _ from 'lodash';
import './style.css';

import { search } from '../../actions/';

class SearchBar extends Component {
  constructor(props) {
    super(props);
    this.state = { inputValue: '' };

    this.startSearch = _.debounce(this.startSearch, 2000);
  }

  startSearch(query) {
    const storedTerms = this.props.searchedTerm;
    let foundDuplicate = false;

    if (storedTerms.length === 0 && query) {
      return this.props.search(query);
    }

    if (storedTerms.length !== 0 && query) {
      const testDuplicate = storedTerms.map(term => term === query);
      foundDuplicate = testDuplicate.some(element => element);
    }

    if (storedTerms.length !== 0 && !query) {
      return false;
    }

    if (foundDuplicate) {
      return false;
    }

    return this.props.search(query);
  }

  onChange = ({ target: { value } }) => {
    this.setState({ inputValue: value });
    if (value.length > 2) {
      this.startSearch(value);
    }
  };

  render() {
    return (
      <div className="Search-bar">
        <input
          placeholder="Type something to search GitHub"
          value={this.state.inputValue}
          onChange={this.onChange}
        />
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    searchedTerm: state.searchedTerm,
    savedData: state.savedData,
  };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ search }, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);

Last Bug to deal with

But it has one last bug, that I don't know how to get rid off. When the user wants to change search query and uses backspace to erase search field, my app always fires unexpectedly another API request. Here is an example:

https://youtu.be/uPEt0hHDOAI

Any ideas how I can get rid of that behavior?

like image 772
MountainConqueror Avatar asked Dec 31 '17 22:12

MountainConqueror


People also ask

How do you use debounce in react?

Method 2: Using lodash Another way to implement debouncing is using lodash. Lodash provides a debounce method that we can use to limit the rate of execution of the handleChange function. Just wrap the callback function with the debounce method and provide the amount of delay we want between two events.

What does lodash debounce do?

What is a debounced function? A debounced function is a function that delays its execution a certain amount of milliseconds after the last call was received.

Why we use debounce in react?

If we type the first character, that is 8, we will send request to the backend server. Then we type 0, and we will send another request to the server, and so on. This calls the API so many times, and in turn overuses the requests. So, to prevent this, we use something called a debounce function.


1 Answers

Well this is easy with lodash _.debounce.
You wrap your method with it and pass the milliseconds you want to wait.
As for the minimum length of the input, just invoke the new method only if the length is above 2.

Here is a small running example:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: '',
      inputValue: ''
    };

    this.updateMessage = _.debounce(this.updateMessage, 2000);
  }


  onChange = ({ target: { value } }) => {
    this.setState({ inputValue: value });
    if (value.length > 2) {
      this.updateMessage(value);
    }
  }


  updateMessage = message => this.setState({ message });

  render() {
    const { message, inputValue } = this.state;
    return (
      <div>
        <input placeholder="type something..." value={inputValue} onChange={this.onChange} />
        <hr/>
        <div>server call >> wait 2 seconds & min length of 2</div>
        <p>{message}</p>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
<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>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.compat.js"></script>
<div id="root"></div>
like image 183
Sagiv b.g Avatar answered Sep 17 '22 19:09

Sagiv b.g