Autocomplete Search Bar in React
Building the autocomplete in React with hooks, debounce, and keyboard navigation.
Autocomplete Search Bar in React
Building the autocomplete in React with hooks.
Component
import React, { useState, useEffect, useRef, useCallback } from "react"; function Autocomplete({ fetchSuggestions, onSelect }) { const [query, setQuery] = useState(""); const [suggestions, setSuggestions] = useState([]); const [highlight, setHighlight] = useState(-1); const [isLoading, setIsLoading] = useState(false); const [isOpen, setIsOpen] = useState(false); const containerRef = useRef(null); const debounceRef = useRef(null); useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current); if (!query) { setSuggestions([]); setIsOpen(false); return; } debounceRef.current = setTimeout(async () => { setIsLoading(true); const results = await fetchSuggestions(query); setSuggestions(results); setIsLoading(false); setIsOpen(true); setHighlight(-1); }, 300); return () => clearTimeout(debounceRef.current); }, [query, fetchSuggestions]); useEffect(() => { const handler = (e) => { if (containerRef.current && !containerRef.current.contains(e.target)) { setIsOpen(false); } }; document.addEventListener("click", handler); return () => document.removeEventListener("click", handler); }, []); const handleKeyDown = (e) => { if (e.key === "ArrowDown") { e.preventDefault(); setHighlight(h => Math.min(h + 1, suggestions.length - 1)); } if (e.key === "ArrowUp") { e.preventDefault(); setHighlight(h => Math.max(h - 1, 0)); } if (e.key === "Enter" && highlight >= 0) { e.preventDefault(); onSelect(suggestions[highlight]); setIsOpen(false); } if (e.key === "Escape") { setIsOpen(false); } }; return ( <div className="autocomplete" ref={containerRef}> <input type="text" value={query} onChange={(e) => setQuery(e.target.value)} onKeyDown={handleKeyDown} placeholder="Search..." /> {isOpen && ( <ul className="suggestions"> {isLoading && <li className="loading">Loading...</li>} {!isLoading && suggestions.length === 0 && <li className="no-results">No results</li>} {suggestions.map((item, i) => ( <li key={i} className={`suggestion-item ${i === highlight ? "highlighted" : ""}`} onClick={() => { onSelect(item); setIsOpen(false); setQuery(item); }} > {item} </li> ))} </ul> )} </div> ); }
The Takeaway
React autocomplete: useState (query, suggestions, highlight, isLoading, isOpen), useEffect (debounce with setTimeout, click-outside listener), useRef (container, debounce timer), handleKeyDown (arrow keys, enter, escape). Reusable component with fetchSuggestions and onSelect props.
Use useState for query, suggestions, highlight, isLoading, isOpen. Use useEffect for debounce (setTimeout in useEffect with query dependency) and click-outside listener. Use useRef for the container and debounce timer. Handle keyboard navigation in handleKeyDown.
Use useEffect with the query as a dependency. Set a setTimeout inside useEffect. Return () => clearTimeout(timer) as the cleanup. This creates a debounced effect that runs 300ms after the query changes.
Use useEffect to add a document click listener. Check if the click is inside the container using a ref: if (containerRef.current && !containerRef.current.contains(e.target)) setIsOpen(false). Remove the listener in the cleanup.
Add onKeyDown to the input. Use setHighlight with a functional update: ArrowDown -> setHighlight(h => Math.min(h + 1, suggestions.length - 1)). Enter -> onSelect(suggestions[highlight]). Escape -> setIsOpen(false).
fetchSuggestions (async function that returns an array) and onSelect (callback when a suggestion is selected). This makes the component reusable. The parent provides the data source and handles the selection.
Ready to master React completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master React
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

