Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lodash debounce in React functional component not working

I have a functional component built around the React Table component that uses the Apollo GraphQL client for server-side pagination and searching. I am trying to implement debouncing for the searching so that only one query is executed against the server once the user stops typing with that value. I have tried the lodash debounce and awesome debounce promise solutions but still a query gets executed against the server for every character typed in the search field.

Here is my component (with irrelevant info redacted):

import React, {useEffect, useState} from 'react'; import ReactTable from "react-table"; import _ from 'lodash'; import classnames from 'classnames'; import "react-table/react-table.css"; import PaginationComponent from "./PaginationComponent"; import LoadingComponent from "./LoadingComponent"; import {Button, Icon} from "../../elements"; import PropTypes from 'prop-types'; import Card from "../card/Card"; import './data-table.css';  import debounce from 'lodash/debounce';  function DataTable(props) {     const [searchText, setSearchText] = useState('');      const [showSearchBar, setShowSearchBar] = useState(false);      const handleFilterChange = (e) => {         let searchText = e.target.value;         setSearchText(searchText);         if (searchText) {             debounceLoadData({                 columns: searchableColumns,                 value: searchText             });         }     };      const loadData = (filter) => {         // grab one extra record to see if we need a 'next' button         const limit = pageSize + 1;         const offset = pageSize * page;          if (props.loadData) {             props.loadData({                 variables: {                     hideLoader: true,                     opts: {                         offset,                         limit,                         orderBy,                         filter,                         includeCnt: props.totalCnt > 0                     }                 },                 updateQuery: (prev, {fetchMoreResult}) => {                     if (!fetchMoreResult) return prev;                     return Object.assign({}, prev, {                         [props.propName]: [...fetchMoreResult[props.propName]]                     });                 }             }).catch(function (error) {                 console.error(error);             })         }     };      const debounceLoadData = debounce((filter) => {         loadData(filter);     }, 1000);      return (         <div>             <Card style={{                 border: props.noCardBorder ? 'none' : ''             }}>                 {showSearchBar ? (                         <span className="card-header-icon"><Icon className='magnify'/></span>                         <input                             autoFocus={true}                             type="text"                             className="form-control"                             onChange={handleFilterChange}                             value={searchText}                         />                         <a href="javascript:void(0)"><Icon className='close' clickable                                                            onClick={() => {                                                                setShowSearchBar(false);                                                                setSearchText('');                                                            }}/></a>                 ) : (                         <div>                            {visibleData.length > 0 && (                                 <li className="icon-action"><a  href="javascript:void(0)"><Icon className='magnify' onClick= {() => {     setShowSearchBar(true);     setSearchText(''); }}/></a>                                 </li>                             )}                         </div>                     )                 )}                 <Card.Body className='flush'>                     <ReactTable                         columns={columns}                         data={visibleData}                     />                 </Card.Body>             </Card>         </div>     ); }  export default DataTable 

... and this is the outcome: link

like image 410
jrkt Avatar asked Apr 10 '19 15:04

jrkt


People also ask

How do you use debounce in a functional component?

Debouncing enforces that there is a minimum time gap between two consecutive invocations of a function call. For example, a debounce interval of 500ms means that if 500ms hasn't passed from the previous invocation attempt, we cancel the previous invocation and schedule the next invocation of the function after 500ms.

How do you debounce a function in React?

Here's a simple implementation : import React, { useCallback } from "react"; import { debounce } from "lodash"; const handler = useCallback(debounce(someFunction, 2000), []); const onChange = (event) => { // perform any event related action here handler(); };

What is Debouncing in Javascript?

Debouncing is a programming practice used to ensure that time-consuming tasks do not fire so often, that it stalls the performance of the web page. In other words, it limits the rate at which a function gets invoked.

Can we use useEffect in functional component?

useEffect() is for side-effects. A functional React component uses props and/or state to calculate the output. If the functional component makes calculations that don't target the output value, then these calculations are named side-effects.


2 Answers

debounceLoadData will be a new function for every render. You can use the useCallback hook to make sure that the same function is being persisted between renders and it will work as expected.

useCallback(debounce(loadData, 1000), []); 

const { useState, useCallback } = React;  const { debounce } = _;    function App() {    const [filter, setFilter] = useState("");    const debounceLoadData = useCallback(debounce(console.log, 1000), []);      function handleFilterChange(event) {      const { value } = event.target;        setFilter(value);      debounceLoadData(value);    }      return <input value={filter} onChange={handleFilterChange} />;  }    ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>    <div id="root"></div>
like image 186
Tholle Avatar answered Sep 17 '22 12:09

Tholle


To add onto Tholle's answer: if you want to make full use of hooks, you can use the useEffect hook to watch for changes in the filter and run the debouncedLoadData function when that happens:

const { useState, useCallback, useEffect } = React; const { debounce } = _;  function App() {   const [filter, setFilter] = useState("");   const debounceLoadData = useCallback(debounce(fetchData, 1000), []);    useEffect(() => {     debounceLoadData(filter);   }, [filter]);    function fetchData(filter) {     console.log(filter);   }    return <input value={filter} onChange={event => setFilter(event.target.value)} />; }  ReactDOM.render(<App />, document.getElementById("root")); 
like image 28
Adriaan Marain Avatar answered Sep 18 '22 12:09

Adriaan Marain