React Search Bar Tutorial | React Table Filter
React Search Bar Tutorial | React Table Filter

In this post I want to show you how to create a search bar inside React with synchronous and asynchronous data. And actually it is a much more difficult task than you might think.

Preparation

So here I generated a react-react-app project and the only thing that I added is an array of data on the top.

// src/App.js
const initialArticles = [
  {
    id: "1",
    slug: "/posts/5-tips-to-be-a-better-programmer",
    title: "5 Tips to Be a Better Programmer",
  },
  {
    id: "2",
    slug: "/posts/how-to-center-elements-using-css",
    title: "How to Center Elements Using CSS",
  },
  {
    id: "3",
    slug: "/posts/how-to-validate-javascript-forms-for-beginners",
    title: "How to Validate Javascript Forms for Beginners",
  },
  {
    id: "4",
    slug: "/posts/sort-array-method-you-need-to-know-this-cases",
    title: "Sort Array Method - You Need to Know This Cases",
  },
  {
    id: "5",
    slug: "/posts/25-coding-terms-for-beginners",
    title: "25 Coding Terms for Beginners",
  },
];

const App = () => {
  return (
    <div className="container">
    </div>
  );
};

export default App;

As you can see initialArticles is just an array of objects with id, slug and title. This are articles that we will render.

Also we need to inject in our project CSS that I already prepared for us.

.container {
  margin: 0 auto;
  width: 500px;
  margin-top: 50px;
}

.searchBar {
  display: flex;
  align-items: center;
  justify-content: center;
}

.searchBarClear {
  margin-left: 10px;
  cursor: pointer;
}

.searchBarInput {
  heigth: 30px;
  width: 300px;
  font-size: 16px;
}

.articlesTable {
  margin-top: 80px;
  text-align: center;
}

.article {
  margin: 10px 0;
}

Articles table

Now I want to create 2 new components. ArticleTable to render a list of our articles and SearchBar where the whole logic of searching will be implemented.

Why do we want to split it like that? In the normal application you won't pack everything in a single component. It doesn't make any sense. With such splitting you will see the difficulties with passing data and updating it.

First of all let's create our ArticleTable.

const ArticleTable = ({ articles }) => {
  return (
    <div className="articlesTable">
      {articles.map((article) => (
        <div className="article" key={article.id}>
          {article.title}
        </div>
      ))}
    </div>
  );
};

export default ArticleTable;

So here we just get a list of articles from props and render their titles. We also used articlesTable class from the CSS that we added earlier.

Now let's render it inside our App.js

import ArticleTable from "./ArticleTable";
...
const App = () => {
  return (
    <div className="container">
      <ArticleTable articles={initialArticles} />
    </div>
  );
};

export default App;

As you can see in browser we successfully rendered our initial articles.

Article table

Basic search bar

Now let's create our second component and it will be our search bar.

// src/SearchBar.js
const SearchBar = () => {
  return (
    <form className="searchBar">
      <input
        type="text"
        className="searchBarInput"
      />
    </form>
  );
};
export default SearchBar;

And we must register it inside our App.js

import ArticleTable from "./ArticleTable";
import SearchBar from "./SearchBar";
...
const App = () => {
  return (
    <div className="container">
      <SearchBar />
      <ArticleTable articles={initialArticles} />
    </div>
  );
};

export default App;

As you can see in browser our basic input is already rendered.

Basic search bar

Search bar architecture

First of all we must plan what we will implement. After we type something in our input and hit enter we must notify our parent that we want to apply search. Then our App component must update the list of articles to filter them.

Which actually means our App component must filter our articles and pass only filtered articles in ArticleTable for rendering.

This is why let's create a state with filtered articles in our App component.

...
const App = () => {
  const [articles, setArticles] = useState(initialArticles);
  return (
    <div className="container">
      <SearchBar />
      <ArticleTable articles={articles} />
    </div>
  );
};

export default App;

It works exactly the same but we provide our data through the state now.

Now we need to improve our search bar. And actually we need to create a state inside search bar. Why? At the moment when we are typing in the input we want to save this information inside our SearchBar state.

But only when we submit a form we want to propagate this value to the outside.

import { useState } from "react";

const SearchBar = () => {
  const [innerValue, setInnerValue] = useState("");
  return (
    <form className="searchBar">
      <input
        type="text"
        className="searchBarInput"
        value={innerValue}
        onChange={(e) => setInnerValue(e.target.value)}
      />
    </form>
  );
};
export default SearchBar;

We created innerValue and saved there our input value when we are typing something. But now at some moment we want to submit this form.

import { useState } from "react";

const SearchBar = ({callback}) => {
  const [innerValue, setInnerValue] = useState("");
  const handleSubmit = e => {
    e.preventDefault()
    callback(innerValue)
  }
  return (
    <form className="searchBar" onSubmit={handleSubmit}>
      <input
        type="text"
        className="searchBarInput"
        value={innerValue}
        onChange={(e) => setInnerValue(e.target.value)}
      />
    </form>
  );
};
export default SearchBar;

Here we added handleSubmit to our form and called a callback function with innerValue when it happens. How it is working all together?

  • We have an input and when we type something we changed an innerValue
  • We also have a form and when we submit it we call callback method that we must get from the props

This actually means that inside App component we must provide a callback to our SearchBar. And what we want to change with this callback is our articles state. But we don't want to change it directly but just to filter it by search value.

So we must also save search value in the parent.

const App = () => {
  const [articles, setArticles] = useState(initialArticles);
  const [searchValue, setSearchValue] = useState("");
  console.log('searchValue', searchValue)

  return (
    <div className="container">
      <SearchBar callback={(searchValue) => setSearchValue(searchValue)} />
      <ArticleTable articles={articles} />
    </div>
  );
};

Now every single time when we submit a form we call this callback and set a searchValue.

This is why I said that it is a little bit tricky. Because we have 2 different states for our search inside SearchBar and outside SearchBar.

Search value state

As you can see our search value in App component was updated.

Filtering articles

Now we want to filter our articles every time when we update searchValue in App component.

// src/App.js
const filterArticles = (searchValue) => {
  if (searchValue === "") {
    return initialArticles;
  }
  return initialArticles.filter((article) =>
    article.title.toLowerCase().includes(searchValue.toLowerCase())
  );
};

useEffect(() => {
  const filteredArticles = filterArticles(searchValue);
  setArticles(filteredArticles);
}, [searchValue]);

Here we created a useEffect on our searchValue. It will be called every time when it is changed. Inside we filter our articles by searchValue and update our articles state.

We make filtering by calling filterArticles with searchValue. Inside we simply return all articles if search value is empty or filter our articles by title.

Filtering articles

As you can see we typed 5 hit enter and our articles are filtered and re rendered accordingly.

Async search

What we did is an example of good React architecture. So our application is fully working but we just worked with synchronous data. Inside real application typically you will have an API and asynchronous data. But with our awesome architecture we can easily adjust our code to work with API.

To simulate and API call we can just create a function with setTimeout which will return delayed articles for us.

// src/App.js
const fetchArticles = (searchValue) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      if (searchValue === "") {
        resolve(initialArticles);
        return;
      }
      const filteredArticles = initialArticles.filter((article) =>
        article.title.toLowerCase().includes(searchValue.toLowerCase())
      );
      resolve(filteredArticles);
    }, 2000);
  });
};

Here we created fetchArticles which does exactly the same like filterArticles previously. The only different is that it is a promise which is resolved in 2 seconds. So this is a perfect simulation of API.

Now we need to change our App component a little bit. We won't have any articles at the beginning because we get them from API.

const [articles, setArticles] = useState([]);

Secondly in useEffect we want to get data from API and not just filter them.

useEffect(() => {
  setArticles([]);
  fetchArticles(searchValue).then((articles) => {
    setArticles(articles);
  });
}, [searchValue]);

Here we emptied our articles first and make an API call. After we get results we just update the list of articles like we did previously.

As you can see in browser this code works out of the box. By default we don't see any articles, then after after 2 seconds all articles are rendered because searchValue is set to empty string.

So by building good architecture you can easily work with synchronous or asynchronous data. And actually if you are interested to learn how to build an image slider in React make sure to check this post also.

📚 Source code of what we've done