How to Build a Search Filter using React and React Hooks

In today‘s data-driven world, search filters have become an essential feature in web applications. They allow users to quickly find relevant information from a large dataset, improving the overall user experience. In this article, we‘ll explore how to build a search filter using React and React hooks.

Setting up the Project

Before we dive into the implementation, let‘s set up a new React project. Open your terminal and run the following command:

npx create-react-app search-filter-demo
cd search-filter-demo

This will create a new React project named "search-filter-demo" and navigate you into the project directory.

Next, we‘ll install Axios, a popular library for making HTTP requests in JavaScript. Run the following command in your terminal:

npm install axios

Fetching Data from an API

For this example, we‘ll use the JSONPlaceholder API to fetch a list of users. Create a new file called App.js in the src directory and add the following code:

import React, { useState, useEffect } from ‘react‘;
import axios from ‘axios‘;

function App() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const fetchUsers = async () => {
      const response = await axios.get(‘https://jsonplaceholder.typicode.com/users‘);
      setUsers(response.data);
    };

    fetchUsers();
  }, []);

  return (
    <div>

      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

In this code, we use the useState hook to create a state variable called users, which will store the list of users fetched from the API. We also use the useEffect hook to make an asynchronous request to the API using Axios when the component mounts.

Once the data is fetched, we update the users state using the setUsers function, which triggers a re-render of the component. Finally, we display the list of users in the UI using the map function.

Creating the Search Input Component

Now, let‘s create a search input component that will allow users to enter a search query. Create a new file called SearchInput.js in the src directory and add the following code:

import React from ‘react‘;

function SearchInput({ searchQuery, setSearchQuery }) {
  return (
    <input
      type="text"
      placeholder="Search users..."
      value={searchQuery}
      onChange={e => setSearchQuery(e.target.value)}
    />
  );
}

export default SearchInput;

In this component, we accept two props: searchQuery and setSearchQuery. The searchQuery prop represents the current search query entered by the user, and the setSearchQuery prop is a function that updates the search query state in the parent component.

We render an input element with a type of "text" and a placeholder of "Search users…". We also bind the value prop to the searchQuery state and the onChange event to the setSearchQuery function, which allows us to update the search query state whenever the user types in the input field.

Implementing the Search Functionality

Now that we have the search input component, let‘s implement the search functionality in the App component. Update the App.js file with the following code:

import React, { useState, useEffect } from ‘react‘;
import axios from ‘axios‘;
import SearchInput from ‘./SearchInput‘;

function App() {
  const [users, setUsers] = useState([]);
  const [searchQuery, setSearchQuery] = useState(‘‘);

  useEffect(() => {
    const fetchUsers = async () => {
      const response = await axios.get(‘https://jsonplaceholder.typicode.com/users‘);
      setUsers(response.data);
    };

    fetchUsers();
  }, []);

  const filteredUsers = users.filter(user =>
    user.name.toLowerCase().includes(searchQuery.toLowerCase())
  );

  return (
    <div>

      <SearchInput searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
      <ul>
        {filteredUsers.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

Here‘s what we‘ve changed:

  1. We added a new state variable called searchQuery to store the current search query entered by the user.
  2. We imported the SearchInput component and rendered it inside the App component, passing the searchQuery and setSearchQuery props.
  3. We created a new variable called filteredUsers, which filters the users array based on the searchQuery. We use the filter method to create a new array that only includes users whose names contain the search query (case-insensitive).
  4. Finally, we update the map function to iterate over the filteredUsers array instead of the users array, so that only the filtered users are displayed in the UI.

Optimizing the Search Filter

While the current implementation works, there are a few optimizations we can make to improve performance and user experience:

  1. Debounce: When the user types quickly in the search input, the filter function is called on every keystroke, which can be expensive for large datasets. We can use a debounce function to delay the execution of the filter function until the user has stopped typing for a certain amount of time (e.g., 300ms).

  2. Case-insensitive search: In the current implementation, we convert both the user‘s name and the search query to lowercase before comparing them. This ensures that the search is case-insensitive. However, we can optimize this further by using a regular expression with the i flag, which performs a case-insensitive search.

Here‘s an updated version of the App component with these optimizations:

import React, { useState, useEffect } from ‘react‘;
import axios from ‘axios‘;
import SearchInput from ‘./SearchInput‘;

function debounce(func, delay) {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(...args), delay);
  };
}

function App() {
  const [users, setUsers] = useState([]);
  const [searchQuery, setSearchQuery] = useState(‘‘);

  useEffect(() => {
    const fetchUsers = async () => {
      const response = await axios.get(‘https://jsonplaceholder.typicode.com/users‘);
      setUsers(response.data);
    };

    fetchUsers();
  }, []);

  const filteredUsers = users.filter(user =>
    new RegExp(searchQuery, ‘i‘).test(user.name)
  );

  const debouncedSetSearchQuery = debounce(setSearchQuery, 300);

  return (
    <div>

      <SearchInput searchQuery={searchQuery} setSearchQuery={debouncedSetSearchQuery} />
      <ul>
        {filteredUsers.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

In this updated version, we:

  1. Created a debounce function that takes a function and a delay as arguments and returns a new function that delays the execution of the original function by the specified delay.
  2. Updated the filteredUsers variable to use a regular expression with the i flag for case-insensitive search.
  3. Created a new variable called debouncedSetSearchQuery that wraps the setSearchQuery function with the debounce function, specifying a delay of 300ms.
  4. Updated the SearchInput component to use the debouncedSetSearchQuery function instead of the setSearchQuery function.

With these optimizations, the search filter will perform better and provide a smoother user experience, especially for large datasets.

Styling the Search Filter

To make the search filter visually appealing, we can add some basic styling using CSS. Create a new file called App.css in the src directory and add the following code:

.app {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

h1 {
  text-align: center;
}

input {
  width: 100%;
  padding: 10px;
  font-size: 16px;
  margin-bottom: 20px;
}

ul {
  list-style-type: none;
  padding: 0;
}

li {
  background-color: #f4f4f4;
  padding: 10px;
  margin-bottom: 10px;
  border-radius: 4px;
}

In this CSS file, we:

  1. Set a max-width for the app class and center it horizontally using margin: 0 auto.
  2. Center the h1 heading using text-align: center.
  3. Style the search input to take up the full width of its container, add some padding, and set a font size.
  4. Remove the default list styling from the ul element using list-style-type: none and remove the default padding.
  5. Style the li elements with a light gray background color, some padding, margin bottom, and rounded corners using border-radius.

Now, update the App.js file to import the CSS file and apply the app class to the top-level div:

import React, { useState, useEffect } from ‘react‘;
import axios from ‘axios‘;
import SearchInput from ‘./SearchInput‘;
import ‘./App.css‘;

// ... rest of the code ...

function App() {
  // ... rest of the code ...

  return (
    <div className="app">

      <SearchInput searchQuery={searchQuery} setSearchQuery={debouncedSetSearchQuery} />
      <ul>
        {filteredUsers.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

With these changes, the search filter should now have a more polished and user-friendly appearance.

Conclusion

In this article, we learned how to build a search filter using React and React hooks. We covered the following topics:

  1. Setting up a React project and installing necessary dependencies
  2. Fetching data from an API using Axios and storing it in state
  3. Creating a search input component and handling user input
  4. Implementing the search functionality using React hooks (useState and useEffect)
  5. Filtering the data based on the search query and updating the UI
  6. Optimizing the search filter for better performance using debounce and regular expressions
  7. Styling the search filter component using CSS

By following the steps outlined in this article, you should now have a solid understanding of how to build a search filter in React. Remember to keep performance and user experience in mind when implementing search filters, and don‘t hesitate to experiment with different optimization techniques and styling options to create the best possible search experience for your users.

Happy coding!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *