Autocomplete and search functionalities are common features in web forms, allowing users to search and select from a list of options quickly. These features enhance the user experience by providing suggestions as the user types and reducing the chance of errors.
In React, you can implement autocomplete and search in forms using native HTML elements or third-party libraries. Let’s explore different methods of adding these features to React forms.
1. Autocomplete Using Native HTML datalist
Element
HTML provides a built-in datalist
element that can be used for autocomplete functionality. This works well for simple autocomplete needs.
Example: Simple Autocomplete with datalist
import React, { useState } from 'react';
const AutocompleteForm = () => {
const [selectedOption, setSelectedOption] = useState('');
const options = ['Apple', 'Banana', 'Orange', 'Grape', 'Pineapple'];
const handleChange = (e) => {
setSelectedOption(e.target.value);
};
return (
<form>
<label htmlFor="fruit">Select a fruit: </label>
<input
list="fruits"
id="fruit"
name="fruit"
value={selectedOption}
onChange={handleChange}
/>
<datalist id="fruits">
{options.map((option, index) => (
<option key={index} value={option} />
))}
</datalist>
</form>
);
};
export default AutocompleteForm;
Explanation:
- The
datalist
element provides a list of predefined options for the input field. - As the user types, the input will show suggestions based on the available options.
- This is a simple and native solution for autocomplete.
2. Autocomplete with Search Using react-select
For more complex search and autocomplete features (e.g., async data fetching, custom styling), libraries like react-select
offer a highly customizable and feature-rich solution.
Example: Autocomplete with react-select
npm install react-select
import React, { useState } from 'react';
import Select from 'react-select';
const AutocompleteWithReactSelect = () => {
const [selectedOption, setSelectedOption] = useState(null);
const options = [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
{ value: 'orange', label: 'Orange' },
{ value: 'grape', label: 'Grape' },
{ value: 'pineapple', label: 'Pineapple' },
];
const handleChange = (selected) => {
setSelectedOption(selected);
};
return (
<form>
<label>Select a fruit: </label>
<Select
value={selectedOption}
onChange={handleChange}
options={options}
isClearable
isSearchable
/>
</form>
);
};
export default AutocompleteWithReactSelect;
Explanation:
react-select
is a popular library that provides powerful search, autocomplete, and multi-select capabilities.- The
isSearchable
prop enables the search feature, whileisClearable
allows clearing the selection. - This solution is ideal for more complex cases, such as dynamic data fetching, custom filtering, and styling.
3. Search with API Call (Async Data)
Sometimes, the list of options for autocomplete needs to be fetched dynamically from an API. In such cases, we can use libraries like react-select
along with asynchronous data fetching.
Example: Autocomplete with Async API Call
import React, { useState } from 'react';
import Select from 'react-select';
import axios from 'axios';
const AsyncAutocomplete = () => {
const [options, setOptions] = useState([]);
const [selectedOption, setSelectedOption] = useState(null);
const fetchOptions = async (inputValue) => {
if (!inputValue) return [];
const response = await axios.get(`https://api.example.com/search?q=${inputValue}`);
return response.data.map((item) => ({
value: item.id,
label: item.name,
}));
};
const handleChange = (selected) => {
setSelectedOption(selected);
};
return (
<form>
<label>Select an option: </label>
<Select
value={selectedOption}
onChange={handleChange}
loadOptions={fetchOptions}
cacheOptions
defaultOptions
isClearable
isSearchable
/>
</form>
);
};
export default AsyncAutocomplete;
Explanation:
- Async Data Fetching: The
fetchOptions
function fetches data from an API based on the user’s input. loadOptions
: This prop is used inreact-select
to load options asynchronously.cacheOptions
: This ensures that previously fetched options are cached for improved performance.
4. Custom Autocomplete with useState
and Filter Logic
If you prefer to build a custom autocomplete or search solution without third-party libraries, you can use basic React hooks and filtering logic.
Example: Custom Autocomplete with useState
import React, { useState } from 'react';
const CustomAutocomplete = () => {
const [query, setQuery] = useState('');
const [selectedOption, setSelectedOption] = useState('');
const [filteredOptions, setFilteredOptions] = useState([]);
const options = ['Apple', 'Banana', 'Orange', 'Grape', 'Pineapple'];
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
const filtered = options.filter((option) =>
option.toLowerCase().includes(value.toLowerCase())
);
setFilteredOptions(filtered);
};
const handleSelect = (option) => {
setSelectedOption(option);
setQuery(option);
setFilteredOptions([]);
};
return (
<div>
<label>Select a fruit: </label>
<input
type="text"
value={query}
onChange={handleChange}
placeholder="Search fruits"
/>
{filteredOptions.length > 0 && (
<ul>
{filteredOptions.map((option, index) => (
<li key={index} onClick={() => handleSelect(option)}>
{option}
</li>
))}
</ul>
)}
{selectedOption && <div>Selected: {selectedOption}</div>}
</div>
);
};
export default CustomAutocomplete;
Explanation:
- Custom Search Logic: The
handleChange
function filters the options based on the user’s input and updates thefilteredOptions
state. - Select Option: When an option is clicked, it updates the
selectedOption
state and clears the filtered list.
5. Search with Debouncing
To avoid making API calls on every keystroke (especially in case of slow networks), you can implement debouncing to limit the number of requests or filter operations.
Example: Debounced Search
npm install lodash.debounce
import React, { useState } from 'react';
import debounce from 'lodash.debounce';
const DebouncedSearch = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const search = (query) => {
// Simulate an API call
const filteredResults = ['Apple', 'Banana', 'Orange', 'Grape', 'Pineapple'].filter((item) =>
item.toLowerCase().includes(query.toLowerCase())
);
setResults(filteredResults);
};
const debouncedSearch = debounce((query) => search(query), 500);
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
debouncedSearch(value);
};
return (
<div>
<label>Search Fruits: </label>
<input type="text" value={query} onChange={handleChange} />
<ul>
{results.map((result, index) => (
<li key={index}>{result}</li>
))}
</ul>
</div>
);
};
export default DebouncedSearch;
Explanation:
- Debouncing: Using
lodash.debounce
, thedebouncedSearch
function is called after 500ms of inactivity, which reduces the number of calls to the search function. - Filtering: The list is filtered based on the user’s input, and the results are displayed below the input field.