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.
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.
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
.
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.
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.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done