How to Use React MUI Autocomplete to Build a Searchable Dropdown for Product Search (With Async API)
How to Use React MUI Autocomplete to Build a Searchable Dropdown for Product Search (With Async API)
Before diving into implementation, let's understand what makes the Autocomplete component
special and why it's ideal for product search functionality. Unlike a simple Select component,
Autocomplete offers filtering, free-text entry, and rich customization of both input and
dropdown items, making it perfect for search interfaces where users might not know exactly
what they're looking for.
Filtering Options: As users type, the component can filter through available options.
Asynchronous Data Loading: It can work with data fetched from APIs on-demand.
Custom Rendering: You can customize how options appear in the dropdown.
Multiple Selection: It supports selecting multiple items when needed.
Keyboard Navigation: Users can navigate options using keyboard shortcuts.
Accessibility: Built with accessibility in mind, including proper ARIA attributes.
getOptionLabel function (option) => Used to determine the string value for a given
option.toString() option
Controlled Mode: In controlled mode, you explicitly manage the component's state through
props like value and onChange . This gives you more control but requires more code.
function ControlledAutocomplete() {
const [value, setValue] = useState(null);
return (
<Autocomplete
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
options={['Option 1', 'Option 2', 'Option 3']}
renderInput={(params) => <TextField {...params} label="Controlled" />}
/>
);
}
Uncontrolled Mode: In uncontrolled mode, the component manages its own state internally.
This is simpler but provides less control.
For our product search implementation, we'll use the controlled approach as it gives us more
flexibility when working with async data.
Customization Options
The Autocomplete component offers several ways to customize its appearance and behavior:
<Autocomplete
sx={{
width: 300,
'& .MuiOutlinedInput-root': {
borderRadius: 2,
},
'& .MuiAutocomplete-popupIndicator': {
color: 'primary.main',
}
}}
options={options}
renderInput={(params) => <TextField {...params} label="Products" />}
/>
Theme Customization
You can customize the Autocomplete component globally through the theme:
function App() {
return (
<ThemeProvider theme={theme}>
{/* Your components */}
One of the most powerful customization features is the ability to render custom option
components:
<Autocomplete
options={products}
getOptionLabel={(option) => option.name}
renderOption={(props, option) => (
<li {...props}>
<img
src={option.thumbnail}
alt={option.name}
style={{ width: 40, marginRight: 10 }}
/>
<div>
<div>{option.name}</div>
<div style={{ fontSize: 12, color: 'gray' }}>${option.price}</div>
</div>
</li>
)}
renderInput={(params) => <TextField {...params} label="Products" />}
/>
Accessibility Features
The Autocomplete component is built with accessibility in mind. It includes:
ARIA attributes: Proper roles and aria-* attributes for screen readers
Keyboard navigation: Users can navigate options using arrow keys, select with Enter,
and close with Escape
Focus management: Proper focus handling for keyboard users
<Autocomplete
options={products}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
label="Products"
aria-label="Search for products"
InputProps={{
...params.InputProps,
'aria-describedby': 'product-search-description'
}}
/>
)}
/>
<div id="product-search-description" style={{ display: 'none' }}>
Search for products by name, type arrow keys to navigate results
</div>
Installing Dependencies
Next, install the required dependencies:
function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');
return (
<Autocomplete
id="product-search"
options={options}
loading={loading}
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
i b l i i i l ||
This sets up the basic structure with:
State for options, loading status, selected value, and input value
Proper rendering of the input field with a loading indicator
Basic configuration for option labels and equality checks
function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');
A useEffect hook that triggers API calls when the input value changes
Loading state management
A cleanup function to prevent state updates if the component unmounts
Error handling for API requests
function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');
setLoading(true);
function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');
Added a custom option renderer with product images, titles, categories, and prices
Improved the layout with MUI's Box component for flexbox layouts
Added typography variations for better readability
Expanded the width of the component to accommodate the richer content
function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');
const [error, setError] = useState(null);
Advanced Capabilities
Now that we've built a functional product search component, let's explore some advanced
capabilities you can add.
function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');
Custom Filtering
You can implement custom filtering logic for more advanced search capabilities:
function ProductSearch() {
// ... other state and logic
return (
<Autocomplete
filterOptions={filterOptions}
// Other props as before
/>
);
}
Infinite Scrolling
For very large datasets, you can implement infinite scrolling:
import React, { useState, useEffect, useCallback } from 'react';
import { Autocomplete, TextField, CircularProgress, Box } from '@mui/material';
import axios from 'axios';
import InfiniteScroll from 'react-infinite-scroll-component';
function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
setLoading(true);
try {
const response = await axios.get(
`https://dummyjson.com/products/search?q=${query}&skip=${(pageNum - 1) * 20}&limit
&limi
);
Performance Optimization
Debounce Input Changes: Always debounce API calls to prevent excessive requests.
Virtualization for Large Lists: Use react-window for rendering large lists of options.
Lazy Loading Images: Implement lazy loading for product images in the dropdown.
<img
src={option.thumbnail}
alt={option.title}
loading="lazy"
// Other props
/>
Problem: The dropdown appears in the wrong position or gets cut off by the viewport.
function CustomPopper(props) {
return <Popper {...props} placement="bottom-start" />;
}
// In your component
<Autocomplete
PopperComponent={CustomPopper}
// Other props
/>
function ProductForm() {
const formik = useFormik({
initialValues: {
product: null,
},
onSubmit: (values) => {
console.log('Form submitted:', values);
},
});
return (
<form onSubmit={formik.handleSubmit}>
<Autocomplete
id="product"
options={products}
getOptionLabel={(option) => option.title || ''}
value={formik.values.product}
onChange={(_, newValue) => {
formik.setFieldValue('product', newValue);
}}
<Autocomplete
id="product-search"
options={options}
renderInput={(params) => (
<TextField
{...params}
label="Search Products"
aria-label="Search for products"
InputProps={{
...params.InputProps,
'aria-describedby': 'product-search-description'
}}
/>
)}
// Other props
/>
<div id="product-search-description" style={{ display: 'none' }}>
Search for products by name, brand, or category. Use arrow keys to navigate results.
</div>
<Autocomplete
sx={{
'& .MuiAutocomplete-inputRoot': {
borderRadius: 2,
backgroundColor: 'background.paper',
boxShadow: 1,
transition: theme => theme.transitions.create(['box-shadow']),
'&:hover': {
boxShadow: 3,
},
'&.Mui-focused': {
boxShadow: 4,
}
}
}}
// Other props
/>
function ProductSearch() {
const dispatch = useDispatch();
const { products, loading, selectedProduct } = useSelector(state => state.products);
const [inputValue, setInputValue] = React.useState('');
useEffect(() => {
if (inputValue.length >= 2) {
dispatch(searchProducts(inputValue));
}
}, [inputValue, dispatch]);
return (
<Autocomplete
options={products}
loading={loading}
value={selectedProduct}
onChange={(event, newValue) => {
dispatch(selectProduct(newValue));
Wrapping Up
In this comprehensive guide, we've built a robust product search component using MUI's
Autocomplete with asynchronous API integration. We've covered everything from basic
implementation to advanced features like virtualization, caching, and custom styling.
The MUI Autocomplete component offers tremendous flexibility and power for creating
searchable dropdowns. By combining it with proper API integration and performance
optimizations, you can create a smooth, user-friendly product search experience that scales
well with large datasets.
Button