Skip to content

Instantly share code, notes, and snippets.

@jessebarocio
Created January 25, 2021 21:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jessebarocio/326ad8b76de270f4f3c65c3dcfc23834 to your computer and use it in GitHub Desktop.
Save jessebarocio/326ad8b76de270f4f3c65c3dcfc23834 to your computer and use it in GitHub Desktop.
React AutocompleteTextbox component using Bootstrap 4
/* Hide the bootstrap dropdown-menu if it has no children */
.dropdown-menu:empty {
display: none !important;
}
import React, { useState, useCallback, useEffect } from 'react';
import './AutocompleteTextbox.css';
// A simple Autocomplete Textbox component. Allows for loading
const AutocompleteTextbox = ({
id, // id passed to the underlying input element
className, // classes passed to the underlying input element
placeholder, // placeholder for the underlying input element
loadItems, // function to load items given a search query
renderItem, // function to render items in the autocomplete dropdown
onItemSelected, // function to trigger when an autocomplete item is clicked
loadDelay = 500 // amount of time (in ms) to delay loading items after a user stops typing in the input
}) => {
const [query, setQuery] = useState();
const [loading, setLoading] = useState(false);
const [results, setResults] = useState([]);
const loadItemsHandler = useCallback(async () => {
if (query && loadItems) {
// Clear the list and set loading = true (so spinner is visible)
setResults([]);
setLoading(true);
// Fetch the items. NOTE: the parent component should handling fetch errors.
let items = await loadItems(query);
// Set the result list
setLoading(false);
setResults(items);
}
}, [query, loadItems]);
const onItemClickHandler = (event, item) => {
// Cancel out the HTML click event
event.preventDefault();
// If onItemSelected is set then execute it
if (onItemSelected) {
onItemSelected(item);
}
}
// This causes loadItemsHandler execution to be delayed by a configured
// number of milliseconds after the user finishes typing. This prevents
// unnecessary queries/reloads.
useEffect(() => {
const timeOutId = setTimeout(() => loadItemsHandler(), loadDelay);
return () => clearTimeout(timeOutId);
}, [loadItemsHandler, loadDelay]);
return (
<div className="dropdown">
<input type="text"
id={id}
className={'dropdown-toggle ' + className}
data-toggle="dropdown"
placeholder={placeholder}
onChange={event => setQuery(event.target.value)} />
<div className="dropdown-menu">
{loading && (<div className="dropdown-item">
<div className="spinner-grow spinner-grow-sm align-middle" role="status">
<span className="sr-only">Loading...</span>
</div> Loading...
</div>)}
{results.map((item, idx) =>
<button key={idx} className='dropdown-item' onClick={e => onItemClickHandler(e, item)}>
{renderItem ? renderItem(item) : item + ''}
</button>)}
</div>
</div>
);
}
export default AutocompleteTextbox;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment