0% found this document useful (0 votes)
26 views

How to Use React MUI Autocomplete to Build a Searchable Dropdown for Product Search (With Async API)

This document provides a comprehensive guide on building a searchable product dropdown using React's Material-UI Autocomplete component with asynchronous API integration. It covers key features, customization options, and advanced capabilities such as debouncing, error handling, and virtualization for large datasets. By following the steps outlined, developers will learn to create a robust, production-ready product search interface that enhances user experience.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
26 views

How to Use React MUI Autocomplete to Build a Searchable Dropdown for Product Search (With Async API)

This document provides a comprehensive guide on building a searchable product dropdown using React's Material-UI Autocomplete component with asynchronous API integration. It covers key features, customization options, and advanced capabilities such as debouncing, error handling, and virtualization for large datasets. By following the steps outlined, developers will learn to create a robust, production-ready product search interface that enhances user experience.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 20

Building a Searchable Product

Dropdown with React MUI Autocomplete


and Async API
As a front-end developer working with React applications, you've likely encountered the need
for a searchable dropdown that fetches data from an API. The Material-UI (MUI)
Autocomplete component offers a powerful solution for this common requirement, especially
when building product search functionality. In this article, I'll walk you through creating a
robust, production-ready product search dropdown using MUI's Autocomplete component
with asynchronous API integration.

What You'll Learn


By the end of this guide, you'll be able to:

Implement a fully functional product search dropdown with MUI Autocomplete


Connect your Autocomplete to a REST API with proper loading states
Handle asynchronous data fetching with debouncing for performance
Customize the appearance and behavior of your Autocomplete component
Implement advanced features like virtualization for large datasets
Address common issues and apply best practices for production use

Understanding MUI Autocomplete Component


The Autocomplete component is one of the most versatile and complex components in the
Material-UI library. At its core, it's a combination of a text input and a dropdown menu that
provides suggestions as users type. What makes it particularly powerful is its flexibility in
handling various data structures and its extensive customization options.

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.

Key Features and Capabilities


The Autocomplete component offers several features that make it ideal for product search:

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.

Autocomplete Component Deep Dive

Component Props Reference


The Autocomplete component comes with numerous props that control its behavior. Here's a
breakdown of the essential ones you'll need for building a product search:

Prop Type Default Description

options array [] Array of options to display in the dropdown


loading boolean false If true, a loading indicator will be displayed

value any null The value of the Autocomplete component


(controlled)

onChange function - Callback fired when the value changes

getOptionLabel function (option) => Used to determine the string value for a given
option.toString() option

Controlled vs Uncontrolled Usage


The Autocomplete component can be used in both controlled and uncontrolled modes:

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.

import { useState } from 'react';


import { Autocomplete, TextField } from '@mui/material';

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.

import { Autocomplete, TextField } from '@mui/material';


function UncontrolledAutocomplete() {
return (
<Autocomplete
defaultValue={null}
options={['Option 1', 'Option 2', 'Option 3']}
renderInput={(params) => <TextField {...params} label="Uncontrolled" />}
/>
);
}

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:

Styling with the sx Prop

The sx prop provides a shorthand way to define custom styles:

<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:

import { createTheme, ThemeProvider } from '@mui/material/styles';


const theme = createTheme({
components: {
MuiAutocomplete: {
styleOverrides: {
root: {
'& .MuiOutlinedInput-root': {
borderRadius: 8,
}
},
paper: {
boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.1)',
}
}
}
}
});

function App() {
return (
<ThemeProvider theme={theme}>
{/* Your components */}

Custom Option Rendering

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

You can enhance accessibility further by:

<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>

Setting Up Your Project


Let's start by setting up a new React project with Material-UI. If you already have a project,
you can skip to the next section.

Creating a New React Project


First, create a new React application using Create React App:
npx create-react-app product-search-app
cd product-search-app

Installing Dependencies
Next, install the required dependencies:

npm install @mui/material @mui/icons-material @emotion/react @emotion/styled axios

Here's what each package does:

@mui/material and @mui/icons-material : The core Material-UI components and icons


@emotion/react and @emotion/styled : Required for MUI's styling system
axios : For making HTTP requests to our API

Building the Product Search Autocomplete


Now, let's build our product search component step by step.

Step 1: Create the Basic Autocomplete Component


First, let's create a basic Autocomplete component that will serve as the foundation for our
product search:

import React, { useState } from 'react';


import { Autocomplete, TextField, CircularProgress } from '@mui/material';

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

Step 2: Add Asynchronous API Integration


Now, let's add the ability to fetch products from an API as the user types:

import React, { useState, useEffect } from 'react';


import { Autocomplete, TextField, CircularProgress } from '@mui/material';
import axios from 'axios';

function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');

// Fetch products when input value changes


useEffect(() => {
// Don't fetch for empty or very short queries
if (inputValue.length < 2) {
setOptions([]);
return;
}

let active = true;


setLoading(true);
// Fetch products from the API
axios.get(`https://dummyjson.com/products/search?q=
https://dummyjson com/products/search?q=${inputValue}`)

In this step, we've added:

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

Step 3: Implement Debouncing for Better Performance


To avoid making too many API calls as the user types, let's implement debouncing:

import React, { useState, useEffect, useMemo } from 'react';


import { Autocomplete, TextField, CircularProgress } from '@mui/material';
import axios from 'axios';
import { debounce } from '@mui/material/utils';

function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');

// Create a debounced search function


const fetchProducts = useMemo(
() =>
debounce(async (query) => {
// Don't fetch for empty or very short queries
if (query.length < 2) {
setOptions([]);
setLoading(false);
return;
}

setLoading(true);

In this step, we've:

Implemented debouncing using MUI's built-in debounce utility


Added proper cleanup to prevent memory leaks
Added helpful text for loading and no options states

Step 4: Enhance the UI with Custom Option Rendering


Let's make our product search more visually appealing by customizing how options are
displayed:

import React, { useState, useEffect, useMemo } from 'react';


import {
Autocomplete,
TextField,
CircularProgress,
Box,
Typography,
Divider
} from '@mui/material';
import axios from 'axios';
import { debounce } from '@mui/material/utils';

function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');

// Create a debounced search function


const fetchProducts = useMemo(
() =>
debounce(async (query) => {
if (query.length < 2) {

In this step, we've:

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

Step 5: Add Error Handling and User Feedback


Let's enhance our component with better error handling and user feedback:
import React, { useState, useEffect, useMemo } from 'react';
import {
Autocomplete,
TextField,
CircularProgress,
Box,
Typography,
Alert,
Snackbar
} from '@mui/material';
import axios from 'axios';
import { debounce } from '@mui/material/utils';

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);

// Create a debounced search function


const fetchProducts = useMemo(
() =>

In this step, we've:

Added error state and a Snackbar to display error messages


Improved the noOptionsText to provide better guidance
Added a helper text for when the user types only one character
Added an error handler for images that fail to load
Created a dedicated handler for product selection

Step 6: Implement Virtualization for Large Datasets


When dealing with large datasets, rendering all options can cause performance issues. Let's
implement virtualization to handle this:

import React, { useState, useEffect, useMemo } from 'react';


import {
Autocomplete,
TextField,
CircularProgress,
Box,
Typography,
Alert,
Snackbar,
ListSubheader
} from '@mui/material';
import { VariableSizeList } from 'react-window';
import axios from 'axios';
import { debounce } from '@mui/material/utils';

// Component for virtualized list


const VirtualizedListbox = React.forwardRef(function VirtualizedListbox(props, ref) {
const { children, ...other
... } = props;
const itemCount = Array.isArray(children) ? children.length : 0;
const itemSize = 60; // Height of each item

const getItemSize = (index) => {

In this step, we've:

Implemented virtualization using react-window's VariableSizeList


Applied virtualization conditionally when there are more than 10 items
Set a fixed height for each item to ensure proper rendering
Created a custom ListboxComponent for the virtualized list

Step 7: Integrate with a Form and Handle Form Submission


Let's integrate our ProductSearch component with a form to demonstrate a practical use
case:

import React, { useState, useEffect, useMemo } from 'react';


import {
Autocomplete,
TextField,
CircularProgress,
Box,
Typography,
Alert,
Snackbar,
Button,
Paper,
Grid
} from '@mui/material';
import { VariableSizeList } from 'react-window';
import axios from 'axios';
import { debounce } from '@mui/material/utils';

// Component for virtualized list


const VirtualizedListbox = React.forwardRef(function VirtualizedListbox(props, ref) {
const { children, ...other
... } = props;
const itemCount = Array.isArray(children) ? children.length : 0;
const itemSize = 60; // Height of each item

In this step, we've:

Created a complete product order form with quantity input


Added form validation
Implemented a dynamic order summary
Added success and error notifications
Styled the form with Paper and Grid components for a polished look

Advanced Capabilities
Now that we've built a functional product search component, let's explore some advanced
capabilities you can add.

Caching Search Results


To improve performance, you can implement caching for search results:

import React, { useState, useEffect, useMemo, useRef } from 'react';


import { Autocomplete, TextField, CircularProgress } from '@mui/material';
import axios from 'axios';
import { debounce } from '@mui/material/utils';

function ProductSearch() {
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');

// Cache for search results


const cache = useRef({});

// Create a debounced search function with caching


const fetchProducts = useMemo(
() =>
debounce(async (query) => {
if (query.length < 2) {
setOptions([]);
setLoading(false);
return;

Custom Filtering
You can implement custom filtering logic for more advanced search capabilities:

import { Autocomplete, TextField } from '@mui/material';


import { createFilterOptions } from '@mui/material/Autocomplete';

// Custom filter function


const filterOptions = createFilterOptions({
matchFrom: 'any',
stringify: (option) => `${option.title} ${option.brand} ${option.category} ${option.desc
des
});

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);

const fetchProducts = useCallback(async (query, pageNum) => {


if (query.length < 2) return;

setLoading(true);

try {
const response = await axios.get(
`https://dummyjson.com/products/search?q=${query}&skip=${(pageNum - 1) * 20}&limit
&limi
);

Best Practices and Common Issues

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.

Memoize Components and Functions: Use useMemo and useCallback to prevent


unnecessary re-renders.

Implement Caching: Cache API responses to avoid redundant network requests.

Lazy Loading Images: Implement lazy loading for product images in the dropdown.

<img
src={option.thumbnail}
alt={option.title}
loading="lazy"
// Other props
/>

Common Issues and Solutions


Issue 1: Dropdown Positioning Problems

Problem: The dropdown appears in the wrong position or gets cut off by the viewport.

Solution: Use the PopperComponent prop to customize the positioning:

import { Popper } from '@mui/material';

function CustomPopper(props) {
return <Popper {...props} placement="bottom-start" />;
}

// In your component
<Autocomplete
PopperComponent={CustomPopper}
// Other props
/>

Issue 2: Slow Performance with Large Datasets

Problem: The component becomes sluggish with large datasets.

Solution: Implement server-side pagination and virtualization:

// Server-side pagination approach


const fetchProducts = async (query, page = 1, limit = 20) => {
return axios.get(`/api/products?search=${query}&page=${page}&limit=${limit}`);
};

Issue 3: Form Integration Issues


Problem: The Autocomplete doesn't work well with form libraries like Formik or React Hook
Form.

Solution: Create a custom integration:

import { useFormik } from 'formik';


import { Autocomplete, TextField, Button } from '@mui/material';

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);
}}

Issue 4: Accessibility Concerns

Problem: The component may not be fully accessible to all users.

Solution: Enhance accessibility with ARIA attributes and keyboard navigation:

<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>

Styling Best Practices


Use the Theme System: Leverage MUI's theme system for consistent styling:

import { createTheme, ThemeProvider } from '@mui/material/styles';

const theme = createTheme({


components: {
MuiAutocomplete: {
styleOverrides: {
root: {
'& .MuiOutlinedInput-root': {
borderRadius: 8,
}
},
paper: {
boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.1)',
borderRadius: 8,
},
listbox: {
padding: 0,
},
option: {
'&[aria-selected="true"]': {
backgroundColor: 'rgba(25, 118, 210, 0.12)',
},
'&.Mui-focused': {

Use the sx Prop for Component-Specific Styling:

<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
/>

Integration with State Management


For larger applications, you might want to integrate your product search with a state
management solution like Redux:

import React, { useEffect } from 'react';


import { useDispatch, useSelector } from 'react-redux';
import { Autocomplete, TextField, CircularProgress } from '@mui/material';
import { searchProducts, selectProduct } from './productSlice';

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.

Remember to always consider performance, accessibility, and user experience when


implementing search functionality in your applications. With the techniques covered in this
guide, you're well-equipped to build production-ready search interfaces for your React
applications.

Button

Resources Legal Stuff


Blog Terms & Conditions
Docs Privacy Policy
Material UI Acceptable Use Policy
Tailwind CSS
Email Templates

You might also like