Popular Searches
Popular Course Categories
Popular Courses

Search and Filter Application

Search and Filter Application

React JS Practical Projects

Search and Filter Application in React JS

A Search and Filter Application is a React JS project that allows users to find specific information and narrow down displayed results based on search text, categories, prices, status, or other criteria. It is commonly used in e-commerce websites, product catalogs, student directories, employee management systems, dashboards, and job portals.

React JS Training Course: JustAcademy React JS Training Course

Course Demo: Register for React JS Course Demo


1. Learning Objectives

After completing this topic, learners will be able to:

  • Understand search and filtering functionality in React JS.
  • Create functional components using JSX.
  • Manage search input and filter selections using useState().
  • Use JavaScript filter(), map(), and includes() methods.
  • Implement case-insensitive search.
  • Filter data by category and other conditions.
  • Combine multiple search and filter conditions.
  • Display matching results dynamically.
  • Handle empty search results and reset filters.
  • Create a responsive interface using CSS.

2. What Is a Search and Filter Application?

A Search and Filter Application helps users find relevant records from a collection of data. Search generally matches text entered by the user, while filtering narrows the collection according to selected conditions.

Example

Imagine an online store displaying products such as laptops, mobile phones, headphones, and watches. A user can type "phone" into the search field and select the Electronics category. The application then displays only the products that match both conditions.

Search vs. Filter

Feature Search Filter
Purpose Finds records matching a search query. Narrows records using selected criteria.
Input Usually a text field. Dropdown, checkbox, radio button, or range control.
Example Search for "Laptop". Choose the Electronics category.
Result Shows records matching the text. Shows records satisfying the selected conditions.

3. Features of the Application

  • Search by name: Find products or records using text.
  • Category filter: Display items from a selected category.
  • Price filter: Display products within a selected price range.
  • Combined filtering: Apply search and category conditions together.
  • Dynamic results: Update the list when input or filters change.
  • Result counter: Display the number of matching items.
  • Empty state: Show a message when no items match.
  • Clear filters: Reset search text and filter selections.
  • Responsive layout: Adapt the interface to mobile and desktop screens.

4. Technologies Used

Technology Purpose
React JS Builds the interactive user interface.
JavaScript Processes search queries and filtering conditions.
JSX Defines the interface using HTML-like syntax.
HTML Provides input fields, dropdowns, buttons, and result containers.
CSS Styles the application and controls the layout.
useState() Stores search text and filter selections.
filter() Returns records that satisfy the selected conditions.
map() Renders the filtered records as UI elements.
includes() Checks whether text contains a specified substring.

5. Prerequisites

Before creating the application, learners should understand:

  • HTML input fields, labels, buttons, and containers.
  • CSS selectors, flexbox, grid, and responsive design.
  • JavaScript variables, functions, arrow functions, arrays, and objects.
  • Array methods such as filter() and map().
  • String methods such as includes() and toLowerCase().
  • React functional components, JSX, event handling, and Hooks.

6. Understanding Search State in React

In React, state stores values that can change during the application's lifecycle. A search field can be connected to state so that React knows the current query and updates the displayed results whenever the query changes.

import { useState } from 'react';

function SearchExample() {
  const [searchTerm, setSearchTerm] = useState('');

  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={event => setSearchTerm(event.target.value)}
        placeholder="Search items"
      />
      <p>You searched for: {searchTerm}</p>
    </div>
  );
}

export default SearchExample;

Explanation

  • searchTerm stores the text entered by the user.
  • setSearchTerm() updates the search state.
  • value={searchTerm} makes the input a controlled input.
  • onChange updates the state when the user types.
  • React re-renders the component to display the latest value.

7. Creating a React Project

Create a new React project using Vite if you do not already have one. Ensure Node.js and npm are installed.

npm create vite@latest search-filter-app -- --template react
cd search-filter-app
npm install
npm run dev

Open the local URL displayed in your terminal to view the application.

Project Structure

search-filter-app/
├── public/
├── src/
│   ├── App.jsx
│   ├── App.css
│   ├── index.css
│   └── main.jsx
├── package.json
└── index.html
  • App.jsx contains the search and filter logic.
  • App.css contains application-specific styling.
  • index.css contains global CSS rules.
  • main.jsx renders the React application.
  • package.json contains project dependencies and scripts.

8. Complete Search and Filter Application

The following example creates a product catalog with search by product name, category filtering, maximum-price filtering, result counts, and a Clear Filters button.

Step 1: Write App.jsx

import { useState } from 'react';
import './App.css';

const products = [
  { id: 1, name: 'Laptop Pro', category: 'Electronics', price: 65000 },
  { id: 2, name: 'Smartphone X', category: 'Electronics', price: 30000 },
  { id: 3, name: 'Wireless Headphones', category: 'Electronics', price: 2500 },
  { id: 4, name: 'Running Shoes', category: 'Fashion', price: 3500 },
  { id: 5, name: 'Cotton T-Shirt', category: 'Fashion', price: 800 },
  { id: 6, name: 'Office Chair', category: 'Furniture', price: 7500 },
  { id: 7, name: 'Study Desk', category: 'Furniture', price: 12000 },
  { id: 8, name: 'Smart Watch', category: 'Electronics', price: 5000 }
];

const categories = [
  'All',
  'Electronics',
  'Fashion',
  'Furniture'
];

export default function App() {
  const [searchTerm, setSearchTerm] = useState('');
  const [category, setCategory] = useState('All');
  const [maxPrice, setMaxPrice] = useState('100000');

  const filteredProducts = products.filter(product => {
    const matchesSearch = product.name
      .toLowerCase()
      .includes(searchTerm.trim().toLowerCase());

    const matchesCategory =
      category === 'All' || product.category === category;

    const matchesPrice =
      maxPrice === '' || product.price <= Number(maxPrice);

    return matchesSearch && matchesCategory && matchesPrice;
  });

  function clearFilters() {
    setSearchTerm('');
    setCategory('All');
    setMaxPrice('100000');
  }

  return (
    <main className="app-container">
      <header className="page-header">
        <h1>Search and Filter Products</h1>
        <p>Find products that match your preferences.</p>
      </header>

      <section className="filter-panel" aria-label="Product filters">
        <div className="form-field">
          <label htmlFor="search">Search products</label>
          <input
            id="search"
            type="search"
            value={searchTerm}
            onChange={event => setSearchTerm(event.target.value)}
            placeholder="Enter a product name"
          />
        </div>

        <div className="form-field">
          <label htmlFor="category">Category</label>
          <select
            id="category"
            value={category}
            onChange={event => setCategory(event.target.value)}
          >
            {categories.map(item => (
              <option key={item} value={item}>
                {item}
              </option>
            ))}
          </select>
        </div>

        <div className="form-field">
          <label htmlFor="max-price">Maximum price (₹)</label>
          <input
            id="max-price"
            type="number"
            min="0"
            value={maxPrice}
            onChange={event => setMaxPrice(event.target.value)}
            placeholder="Enter maximum price"
          />
        </div>

        <button type="button" onClick={clearFilters}>
          Clear Filters
        </button>
      </section>

      <section aria-live="polite">
        <p className="result-count">
          Showing {filteredProducts.length} of {products.length} products
        </p>

        {filteredProducts.length === 0 ? (
          <p className="empty-message">
            No products found. Try changing your search or filters.
          </p>
        ) : (
          <div className="product-grid">
            {filteredProducts.map(product => (
              <article className="product-card" key={product.id}>
                <h2>{product.name}</h2>
                <p className="product-category">
                  Category: {product.category}
                </p>
                <p className="product-price">
                  ₹{product.price.toLocaleString('en-IN')}
                </p>
              </article>
            ))}
          </div>
        )}
      </section>
    </main>
  );
}

Step 2: Write App.css

.app-container {
  max-width: 1100px;
  margin: 40px auto;
  padding: 24px;
  font-family: Arial, sans-serif;
  color: #1f2937;
}
.page-header {
  text-align: center;
  margin-bottom: 28px;
}
.page-header h1 {
  margin-bottom: 8px;
}
.page-header p {
  color: #6b7280;
}
.filter-panel {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
  align-items: end;
  gap: 16px;
  padding: 20px;
  margin-bottom: 22px;
  background: #ffffff;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
}
.form-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.form-field label {
  font-size: 14px;
  font-weight: bold;
}
.form-field input,
.form-field select {
  width: 100%;
  min-width: 0;
  padding: 11px;
  border: 1px solid #d1d5db;
  border-radius: 7px;
  font-size: 15px;
  background: #ffffff;
}
button {
  padding: 12px 16px;
  border: none;
  border-radius: 7px;
  background: #2563eb;
  color: #ffffff;
  cursor: pointer;
  font-size: 14px;
}
button:hover {
  background: #1d4ed8;
}
button:focus-visible,
input:focus-visible,
select:focus-visible {
  outline: 3px solid #93c5fd;
  outline-offset: 2px;
}
.result-count {
  margin: 20px 0;
  font-weight: bold;
}
.product-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
}
.product-card {
  padding: 20px;
  background: #ffffff;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.05);
}
.product-card h2 {
  margin-top: 0;
  font-size: 19px;
}
.product-category {
  color: #6b7280;
}
.product-price {
  font-size: 18px;
  font-weight: bold;
  color: #2563eb;
}
.empty-message {
  padding: 28px;
  text-align: center;
  background: #ffffff;
  border-radius: 10px;
  color: #6b7280;
}
@media (max-width: 850px) {
  .filter-panel {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .product-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 520px) {
  .app-container {
    margin: 16px auto;
    padding: 14px;
  }
  .filter-panel,
  .product-grid {
    grid-template-columns: 1fr;
  }
}

Step 3: Add Global CSS

In src/index.css, add the following basic styles:

* {
  box-sizing: border-box;
}
body {
  margin: 0;
  min-height: 100vh;
  background: #f3f4f6;
}

Step 4: Run the Application

  1. Save all files.
  2. Run npm run dev in the terminal.
  3. Open the local URL displayed by Vite.
  4. Enter a product name in the search field.
  5. Select a category from the dropdown.
  6. Enter a maximum price to narrow the results.
  7. Click Clear Filters to restore the default settings.
  8. Try a search that matches no products to see the empty-state message.

9. Understanding the filter() Method

The JavaScript filter() method creates a new array containing only those elements that pass a specified condition. In a React application, it is often used to calculate which items should appear in the UI.

const numbers = [10, 20, 30, 40, 50];

const filteredNumbers = numbers.filter(
  number => number > 25
);

console.log(filteredNumbers);
// [30, 40, 50]

In the product application, filter() checks each product against the search text, category, and maximum price conditions.


10. Implementing Case-Insensitive Search

A search should normally match text regardless of whether the user types uppercase or lowercase letters. Use toLowerCase() to normalize both the search query and the product name.

const query = searchTerm.trim().toLowerCase();

const matches = products.filter(product =>
  product.name.toLowerCase().includes(query)
);

For example, searching for "LAPTOP", "Laptop", or "laptop" returns the same matching product. Trimming removes unnecessary whitespace at the beginning and end of the query.


11. Filtering by Multiple Conditions

A search interface often needs to apply several conditions simultaneously. The logical AND operator (&&) ensures that a record must satisfy every required condition.

const filteredProducts = products.filter(product => {
  const matchesName = product.name
    .toLowerCase()
    .includes(searchTerm.trim().toLowerCase());

  const matchesCategory =
    category === 'All' || product.category === category;

  const matchesPrice =
    maxPrice === '' || product.price <= Number(maxPrice);

  return matchesName && matchesCategory && matchesPrice;
});

How it works:

  • matchesName checks whether the product name contains the search query.
  • matchesCategory allows every category when All is selected or matches the chosen category.
  • matchesPrice allows every price when the maximum-price field is empty or checks the numeric limit.
  • The final expression returns true only when all three conditions pass.

When several filters are combined with AND, a product must satisfy all of them to appear in the result.


12. Searching Across Multiple Fields

Sometimes users need to search by more than one property, such as product name and category. You can combine these fields in the search condition.

const query = searchTerm.trim().toLowerCase();

const filteredProducts = products.filter(product =>
  product.name.toLowerCase().includes(query) ||
  product.category.toLowerCase().includes(query)
);

The logical OR operator (||) allows a product to match either its name or its category. You can extend this approach to include descriptions, brands, or other searchable fields.


13. Adding a Minimum Price Filter

A product catalog can include both minimum and maximum price inputs.

const [minPrice, setMinPrice] = useState('0');
const [maxPrice, setMaxPrice] = useState('100000');

const filteredProducts = products.filter(product => {
  const matchesMin =
    minPrice === '' || product.price >= Number(minPrice);

  const matchesMax =
    maxPrice === '' || product.price <= Number(maxPrice);

  return matchesMin && matchesMax;
});

To complete this feature, add controlled number inputs for minPrice and maxPrice to the JSX. For production applications, also validate that both values are finite, non-negative, and that the minimum does not exceed the maximum.


14. Sorting Search Results

Sorting arranges matching results in a chosen order, such as price from low to high. It is different from filtering: filtering decides which records appear, while sorting decides their order.

const sortedProducts = [...filteredProducts].sort(
  (a, b) => a.price - b.price
);

Copying the array before sorting avoids mutating an array that may be derived from React state. You can add a dropdown with options such as Price: Low to High and Price: High to Low.


15. Displaying Result Counts and Empty States

Showing the number of matching records helps users understand the current search results.

<p>
  Showing {filteredProducts.length} of {products.length} products
</p>

{filteredProducts.length === 0 ? (
  <p>No matching products found.</p>
) : (
  <div>
    {filteredProducts.map(product => (
      <article key={product.id}>
        {product.name}
      </article>
    ))}
  </div>
)}

The conditional expression displays a helpful message when no records match and displays the product cards otherwise.


16. Understanding Controlled Inputs

A controlled input receives its current value from React state and updates that state through an event handler.

<input
  type="search"
  value={searchTerm}
  onChange={event => setSearchTerm(event.target.value)}
  placeholder="Search products"
/>

Controlled inputs keep the search field synchronized with React state. The same approach can be used for dropdowns, checkboxes, and number inputs.


17. Important React Concepts Used in This Project

Concept Purpose
Functional Components Organize the user interface and application logic.
JSX Defines the structure of the search interface.
useState() Stores search text and filter selections.
onChange Responds to input and dropdown changes.
filter() Creates an array containing matching records.
map() Renders matching records into UI elements.
includes() Checks for substring matches.
toLowerCase() Enables case-insensitive text matching.
Logical AND (&&) Combines conditions that must all be true.
Logical OR (||) Allows a match against one of multiple fields.
Conditional Rendering Displays results or an empty-state message.
Unique Keys Help React identify records in rendered lists.

18. Common Mistakes and Their Solutions

Mistake Problem Solution
Forgetting to update search state The input does not control the search query correctly. Use value and onChange together.
Case-sensitive matching Uppercase and lowercase searches may behave differently. Normalize both strings with toLowerCase().
Not trimming the query Leading or trailing spaces can cause unexpected matches. Use trim() before matching.
Incorrect condition combination Some products appear despite failing a required filter. Review the AND and OR operators.
Comparing prices as strings Numeric comparisons may behave incorrectly if conversion is omitted. Convert validated input with Number().
Mutating an array during sorting The original array may be changed unexpectedly. Copy the array before calling sort().
Missing keys React may warn when rendering lists. Use a stable, unique identifier as the key.
No empty-state message Users may not understand why results are missing. Display a message when the filtered array is empty.
Storing unnecessary derived state Stored results can become inconsistent with the source data. Calculate filtered results from data and current filter values.
Invalid price values Negative or non-numeric values may produce unexpected results. Validate price input before applying the filter.

19. Best Practices

  • Use descriptive state names such as searchTerm, category, and maxPrice.
  • Keep the original product data separate from the filtered results.
  • Calculate filtered results during rendering when the dataset is small and filtering is inexpensive.
  • Use useMemo() only when profiling shows that recalculating results is expensive or when stable references are needed.
  • Use stable, unique keys when rendering lists.
  • Use controlled inputs for search and filter controls.
  • Show the number of matching results and provide clear empty-state messages.
  • Validate numeric inputs and handle empty values intentionally.
  • Use semantic labels for input fields and accessible focus indicators.
  • Use debouncing for expensive searches or frequent API requests when appropriate.
  • For large datasets, consider server-side search, pagination, or virtualization.
  • Keep the interface responsive and easy to use on mobile devices.

20. Testing Checklist

  • ☐ All products appear when the search field is empty and default filters are selected.
  • ☐ Searching by a complete product name works.
  • ☐ Searching by part of a product name works.
  • ☐ Search works regardless of uppercase or lowercase letters.
  • ☐ Leading and trailing spaces do not cause unexpected results.
  • ☐ Selecting a category displays only matching products.
  • ☐ Applying a maximum price excludes products above the limit.
  • ☐ Search and category filters work together.
  • ☐ Clearing filters restores the default results.
  • ☐ A no-match search displays a useful message.
  • ☐ The result counter updates correctly.
  • ☐ Sorting does not mutate the original product array.
  • ☐ The application works on mobile and desktop screens.
  • ☐ Inputs and controls have accessible labels and keyboard focus indicators.

21. Mini Project Enhancement Ideas

  1. Multi-category filter: Allow users to select more than one category.
  2. Brand filter: Filter products by manufacturer or brand.
  3. Minimum and maximum price: Add a complete price-range filter.
  4. Sorting options: Sort by name, price, or rating.
  5. Search descriptions: Search both product names and descriptions.
  6. Clear individual filters: Reset a single filter without clearing the others.
  7. Pagination: Split large result lists into pages.
  8. API integration: Retrieve product data from a remote API.
  9. Debounced search: Delay expensive searches until the user pauses typing.
  10. Loading and error states: Display messages while fetching data or when a request fails.
  11. Favorites: Allow users to save products to a favorites list.
  12. Responsive product cards: Add images and improved layouts for different screen sizes.

22. Interview Questions and Answers

Q1. What is a Search and Filter Application?

Answer: It is an application that helps users find records by matching search text and narrowing results according to selected conditions.

Q2. What is the difference between search and filter?

Answer: Search usually matches a query against text fields, while filtering narrows results according to conditions such as category, price, or status.

Q3. Which React Hook is used to store the search query?

Answer: The useState() Hook is commonly used to store and update the search query.

Q4. What does the filter() method do?

Answer: It creates a new array containing the elements that satisfy a specified condition.

Q5. What is the purpose of map()?

Answer: It transforms each item in an array into a new value, such as a React element for displaying a product card.

Q6. How can you make a search case-insensitive?

Answer: Convert both the search query and the target text to lowercase before comparing them.

Q7. How do you combine multiple filters?

Answer: Create a Boolean condition for each filter and combine the required conditions using logical operators such as && and ||.

Q8. Why use controlled inputs?

Answer: Controlled inputs keep the displayed field value synchronized with React state.

Q9. Why should the original data be preserved?

Answer: Keeping the source data unchanged allows users to change or clear filters without losing records from the original collection.

Q10. How do you display a no-results message?

Answer: Check whether the filtered array has a length of zero and conditionally display a message.

Q11. What is debouncing?

Answer: Debouncing delays an operation until a specified period has passed without another triggering event. It is useful for reducing frequent expensive searches or API requests while a user types.

Q12. When should search be handled on the server?

Answer: Server-side search is useful for large datasets, database queries, access-controlled data, or cases where downloading every record to the browser would be inefficient.

Q13. Why should each rendered product have a key?

Answer: A stable key helps React identify each item when a list is updated, filtered, or reordered.

Q14. What is the difference between filtering and sorting?

Answer: Filtering determines which items are included, while sorting determines the order in which those items appear.

Q15. Is useMemo() always necessary for filtering?

Answer: No. Simple filtering is usually fine without it. Use memoization when the calculation is expensive or profiling demonstrates a benefit.


23. Practice Exercises

  1. Create a search field that filters a list of student names.
  2. Add a dropdown to filter students by course.
  3. Add a category filter to the product catalog.
  4. Implement a minimum and maximum price filter.
  5. Allow searching by both product name and category.
  6. Add sorting by price from low to high and high to low.
  7. Add a button to clear all filters.
  8. Display the number of matching records.
  9. Add pagination to the results.
  10. Fetch data from an API and implement search, loading, and error states.

24. Quick Revision

Concept Key Point
Search Finds records matching a query.
Filter Narrows records according to conditions.
useState() Stores search text and filter selections.
filter() Returns matching array elements.
map() Renders results into UI elements.
includes() Checks for a substring match.
toLowerCase() Normalizes text for case-insensitive matching.
&& Requires all combined conditions to be true.
|| Allows at least one alternative condition to be true.
Controlled Input Synchronizes the input value with React state.
Stable Key Identifies each item in a rendered list.
Sorting Changes the order of matching results.

25. Official Documentation and Learning Resources


26. Conclusion

A Search and Filter Application is a useful React JS project for learning controlled inputs, state management, array methods, conditional rendering, and dynamic UI updates. By combining text search with category and price filters, learners can build practical interfaces used in e-commerce stores, directories, dashboards, and data-driven websites.

To learn more, visit the JustAcademy React JS Training Course or register for a React JS course demo.

whatsapp