How to Build a Search Bar to Filter Data in React
By Braincuber Team
Published on January 17, 2026
Every application with more than a handful of items needs a search bar. Users expect to type a few characters and instantly see matching results—no page reloads, no waiting. Whether it's a product catalog, a contact list, or a settings page, real-time filtering has become table stakes for modern interfaces.
In React, building this functionality is remarkably straightforward. With useState for tracking input, JavaScript's filter() for matching, and a sprinkle of responsive styling, you can ship a polished search experience in under 50 lines of code. Let's build a product catalog filter from scratch.
What You'll Build: A searchable product list where users can filter items by name in real time. As they type, the list instantly updates to show only matching products.
Why Real-Time Search Matters
Instant Feedback
Users see results as they type. No submit button, no page reload. Just immediate visual response.
Large Data Navigation
When you have hundreds of items, scrolling is painful. Search lets users jump straight to what they need.
Time Savings
Finding a specific item in seconds instead of minutes directly improves user productivity and satisfaction.
Professional Feel
Real-time filtering signals a modern, well-crafted application. It's an expected feature in 2024+.
Prerequisites
- Node.js (v16+) installed
- Basic React knowledge (components, JSX, hooks)
- A code editor (VS Code recommended)
Step 1: Project Setup
Create a new React app or use an existing one:
# Create new React app npx create-react-app product-search cd product-search # Start development server npm start
Step 2: Define Your Data
We'll use a product catalog with more realistic data—each item has a name, category, and price:
const products = [
{ id: 1, name: "Wireless Headphones", category: "Electronics", price: 79.99 },
{ id: 2, name: "Running Shoes", category: "Sports", price: 129.99 },
{ id: 3, name: "Coffee Maker", category: "Kitchen", price: 49.99 },
{ id: 4, name: "Yoga Mat", category: "Sports", price: 29.99 },
{ id: 5, name: "Bluetooth Speaker", category: "Electronics", price: 59.99 },
{ id: 6, name: "Stainless Steel Water Bottle", category: "Kitchen", price: 24.99 },
{ id: 7, name: "Fitness Tracker", category: "Electronics", price: 149.99 },
{ id: 8, name: "Resistance Bands Set", category: "Sports", price: 19.99 },
{ id: 9, name: "Air Fryer", category: "Kitchen", price: 99.99 },
{ id: 10, name: "Noise Cancelling Earbuds", category: "Electronics", price: 199.99 }
];
Step 3: Build the Search Component
Here's the complete implementation. Replace your src/App.js with this code:
import React, { useState } from 'react';
const products = [
{ id: 1, name: "Wireless Headphones", category: "Electronics", price: 79.99 },
{ id: 2, name: "Running Shoes", category: "Sports", price: 129.99 },
{ id: 3, name: "Coffee Maker", category: "Kitchen", price: 49.99 },
{ id: 4, name: "Yoga Mat", category: "Sports", price: 29.99 },
{ id: 5, name: "Bluetooth Speaker", category: "Electronics", price: 59.99 },
{ id: 6, name: "Stainless Steel Water Bottle", category: "Kitchen", price: 24.99 },
{ id: 7, name: "Fitness Tracker", category: "Electronics", price: 149.99 },
{ id: 8, name: "Resistance Bands Set", category: "Sports", price: 19.99 },
{ id: 9, name: "Air Fryer", category: "Kitchen", price: 99.99 },
{ id: 10, name: "Noise Cancelling Earbuds", category: "Electronics", price: 199.99 }
];
function App() {
const [searchTerm, setSearchTerm] = useState("");
// Filter products based on search term
const filteredProducts = products.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
product.category.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div style={{
padding: "40px",
maxWidth: "800px",
margin: "0 auto",
fontFamily: "system-ui, -apple-system, sans-serif"
}}>
<h1 style={{ marginBottom: "8px" }}>Product Catalog</h1>
<p style={{ color: "#666", marginBottom: "24px" }}>
Search by product name or category
</p>
{/* Search Input */}
<div style={{ position: "relative", marginBottom: "24px" }}>
<input
type="text"
placeholder="Search products..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
style={{
width: "100%",
padding: "12px 16px 12px 44px",
fontSize: "16px",
border: "2px solid #e2e8f0",
borderRadius: "8px",
outline: "none",
transition: "border-color 0.2s"
}}
onFocus={(e) => e.target.style.borderColor = "#61DAFB"}
onBlur={(e) => e.target.style.borderColor = "#e2e8f0"}
/>
{/* Search Icon */}
<svg
style={{ position: "absolute", left: "14px", top: "50%", transform: "translateY(-50%)" }}
width="20" height="20" fill="none" stroke="#9ca3af" strokeWidth="2"
>
<circle cx="9" cy="9" r="7" />
<path d="M14 14l4 4" />
</svg>
</div>
{/* Results Count */}
<p style={{ color: "#666", marginBottom: "16px", fontSize: "14px" }}>
Showing {filteredProducts.length} of {products.length} products
</p>
{/* Product List */}
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
{filteredProducts.length > 0 ? (
filteredProducts.map(product => (
<div
key={product.id}
style={{
padding: "16px 20px",
background: "#fff",
border: "1px solid #e2e8f0",
borderRadius: "8px",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
transition: "box-shadow 0.2s"
}}
onMouseEnter={(e) => e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.1)"}
onMouseLeave={(e) => e.currentTarget.style.boxShadow = "none"}
>
<div>
<h3 style={{ margin: 0, fontSize: "16px" }}>{product.name}</h3>
<span style={{
fontSize: "12px",
background: "#e2e8f0",
padding: "2px 8px",
borderRadius: "4px",
marginTop: "4px",
display: "inline-block"
}}>
{product.category}
</span>
</div>
<span style={{ fontWeight: 600, color: "#10b981" }}>
${product.price.toFixed(2)}
</span>
</div>
))
) : (
<div style={{
textAlign: "center",
padding: "40px",
color: "#9ca3af",
background: "#f9fafb",
borderRadius: "8px"
}}>
No products found matching "{searchTerm}"
</div>
)}
</div>
</div>
);
}
export default App;
How It Works
State with useState
We track the user's input with a state variable:
const [searchTerm, setSearchTerm] = useState("");
Every keystroke updates this state, triggering a re-render.
The filter() Method
JavaScript's filter() creates a new array with matching items:
products.filter(product => product.name.toLowerCase().includes(searchTerm.toLowerCase()))
We convert both to lowercase for case-insensitive matching.
The onChange Event
Every keystroke fires the onChange handler:
onChange={(e) => setSearchTerm(e.target.value)}
This updates state, which re-runs the filter, which updates the UI. All in milliseconds.
Bonus: Multi-Field Search
In our example, we search both name and category. Here's the pattern:
// Search across multiple fields with OR logic
const filteredProducts = products.filter(product => {
const term = searchTerm.toLowerCase();
return (
product.name.toLowerCase().includes(term) ||
product.category.toLowerCase().includes(term) ||
product.price.toString().includes(term)
);
});
Performance Tip: For large datasets (1000+ items), consider debouncing the search input to avoid filtering on every keystroke. Libraries like lodash.debounce or use-debounce make this easy.
Best Practices
Search Bar UX Tips:
- Show result count: Tell users how many items match their query.
- Handle empty state: Display a friendly message when no results are found.
- Clear button: Add an X icon to quickly clear the search field.
- Keyboard accessibility: Ensure the input is focusable and works with screen readers.
Conclusion
Building a search bar in React is one of those tasks that looks complex but becomes trivial once you understand the pattern: state for input, filter for matching, render for display. This same approach scales from a 10-item list to thousands of records—just add debouncing for larger datasets. Now you have a reusable pattern for any searchable list in your React applications.
Key Takeaway: Use useState to track input, filter() with includes() for matching, toLowerCase() for case-insensitivity, and onChange for real-time updates. That's the entire pattern.
