Skip to content

Instantly share code, notes, and snippets.

@SherryH
Last active Jun 19, 2021
Embed
What would you like to do?
Autocomplete Fetch Async Data with Debounce
import React, { useRef, useState, useCallback } from 'react';
import classnames from 'classnames';
import lodash from 'lodash';
const useFocus = () => {
const htmlElRef = useRef(null);
const setFocus = () => {
htmlElRef.current && htmlElRef.current.focus();
};
return [htmlElRef, setFocus];
};
const AutocompleteInput = ({ onRowSelect }) => {
const [input, setInput] = useState('');
const [outputs, setOutputs] = useState([]);
const [inputRef, setInputFocus] = useFocus();
const [isLoading, setIsloading] = useState(false);
async function fetchData() {
const result = await fetch(`/search?q=${input}`);
return result.json();
}
const conditionalFetchData = async (input) => {
if (!input) {
setOutputs([]);
} else {
setIsloading(true);
const outputs = await fetchData(input);
setIsloading(false);
setOutputs(outputs);
}
};
const debouncedFetchData = useCallback(
lodash.debounce(conditionalFetchData, 500),
[]
);
const handleChange = (event) => {
setInput(event.target.value);
debouncedFetchData(event.target.value);
};
const handleListClick = (output) => {
setInput(output);
onRowSelect(output);
setOutputs([]);
setInputFocus();
};
return (
<div
className={classnames('autocomplete-control', {
'is-loading': isLoading,
})}
role="search"
>
<input
className="autocomplete-input"
onChange={handleChange}
value={input}
ref={inputRef}
placeholder="Search..."
/>
{outputs.length ? (
<div className="autocomplete-list" role="listbox">
{outputs.map((output, index) => (
<div
key={index}
className="autocomplete-item"
role="option"
onClick={() => handleListClick(output)}
>
{output}
</div>
))}
</div>
) : null}
</div>
);
};
export default AutocompleteInput;
@SherryH

This comment has been minimized.

Copy link
Owner Author

@SherryH SherryH commented Jun 19, 2021

//  -- non working solution --
//  handler calls conditionalFetchData(), which calls the debounced function

  async function fetchData() {
    const result = await fetch(`/search?q=${input}`);
    return result.json();
  }
  const debouncedFetchData = useCallback(lodash.debounce(fetchData, 500), []);
  const conditionalFetchData = async (input) => {
    if (!input) {
      setOutputs([]);
    } else {
      setIsloading(true);
      const outputs = await debouncedFetchData(input);
      console.log({ outputs }); // undefined, because debouncedFetchData is not awaited
      setIsloading(false);
      setOutputs(outputs);
    }
  };

  const handleChange = (event) => {
    setInput(event.target.value);
    conditionalFetchData(event.target.value);
  };
@SherryH

This comment has been minimized.

Copy link
Owner Author

@SherryH SherryH commented Jun 19, 2021

//  working solution 
// with conditionlFetchData calling debouncedFetchData
// using asyncDebounce

 // --- working asyncDebounce Solution ---

  // output: a function which returns a promise. The promise resolves when FuncA in,
  // lodash.debounce(FuncA,500), resolves. (FuncA.then())

  function asyncDebounce(funcA, wait) {
    const debounce = lodash.debounce(async (resolve, reject, args) => {
      // pass in args
      try {
        const result = await funcA(args);
        resolve(result);
      } catch (error) {
        reject(error);
      }
    }, wait);
    return (...args) => {
      return new Promise((resolve, reject) => {
        debounce(resolve, reject, args);
      });
    };
  }
  async function fetchData() {
    const result = await fetch(`/search?q=${input}`);
    return result.json();
  }
  const debouncedFetchData = useCallback(asyncDebounce(fetchData, 500), []);
  const conditionalFetchData = async (input) => {
    if (!input) {
      setOutputs([]);
    } else {
      setIsloading(true);
      const outputs = await debouncedFetchData(input);
      console.log({ outputs }); // outputs console logged after wait period!
      setIsloading(false);
      setOutputs(outputs);
    }
  };

  const handleChange = (event) => {
    setInput(event.target.value);
    conditionalFetchData(event.target.value);
  };
@SherryH

This comment has been minimized.

Copy link
Owner Author

@SherryH SherryH commented Jun 19, 2021

// useEffect for async Data Fetch
  // --- useEffect -----

  const previousInput = useRef(input);

  async function fetchData() {
    const result = await fetch(`/search?q=${input}`);
    return result.json();
  }
  const conditionalFetchData = async (input) => {
    if (!input) {
      setOutputs([]);
    } else {
      setIsloading(true);
      const outputs = await fetchData(input);
      setIsloading(false);
      setOutputs(outputs);
    }
  };
  const debouncedFetchData = useCallback(
    lodash.debounce(conditionalFetchData, 500),
    []
  );
  useEffect(() => {
    if (previousInput.current !== input) {
      debouncedFetchData(input);
      previousInput.current = input;
    }
  }, [input, debouncedFetchData]);

  const handleChange = (event) => {
    setInput(event.target.value);
    setIsloading(true);
  };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment