Dynamic Search With React
Most of you are probably familiar with the notion of dynamic search, even if you don’t know it by that name. It is when a list of search results is updated in real-time as you type the search...
Using React Hooks to Implement a Dynamic Search Bar
Introduction
Most of you are probably familiar with the notion of dynamic search, even if you don’t know it by that name. It is when a list of search results is updated in real-time as you type the search term. This blog post will demonstrate one way to implement dynamic search with React and Typescript. The example we’ll be implementing is searching a list of “user” objects, and displaying some of their data (name and email address). This post assumes the reader has at least basic knowledge of React and Typescript. I will include the complete code at the end of the post and break-out sections for clarification.
In this implementation, we will be using React’s “useState()” hook to manipulate the state rather than creating a class to do so.
In brief, hooks allow us to add React state to a function component. In order to access the useState() hook, we will need to import it. I have chosen to import the :
import * as React from "react";
The format of useState shown below uses “array destructuring” to assign the return value of useState to two variables (in the square brackets):
const [search, setSearch] = React.useState('');
The first item in the array is the current value of the variable in which we will store our search term from the search bar. Most importantly that variable is a property of React's state. The second item is a function that allows us to update the value of search.
The only argument we pass to useState is the initial value, in our case an empty string that will become our search term once the user begins typing in the search bar. One key concept our dynamic search bar relies on is that updating the React state will cause React to re-render the UI elements. This will have the effect of updating the list of users shown below the search bar.
The App() component is our primary component and within it you can see that we are calling additional components called “SearchBar” and “DisplayResults”, the code for which will be shown shortly.
export default function App() {
// the useState hook
const [search, setSearch] = React.useState('');
const updateSearchValue = (search) => {
setSearch(search)
};
const searchResults = searchUsers(users, search);
return (
<div className="App">
<div className="header">
<SearchBar updateSearchValue={updateSearchValue}/>
</div>
<div className="resultsList-div">
<ul className="resultsList">
{searchUsers(users, search).map(r => (
<DisplayResults key = {r.ip_address} {...r} />
))}
</ul>
</div>
</div>
);
};
Calling Display Results in the Return Statement
A couple of notes on how we call DisplayResults
in the return statement:
We are using Javascript’s Array.prototype.map() function to call DisplayResults for each of the items in the return value of the searchUsers function. We use the “...” spread operator to pass all elements of the object "r" as props, which we later reference by name:function DisplayResults({last_name, first_name, email})
We need a unique key for each <li> element, which is what the DisplayResults component returns. I made the assumption that the user's IP address would be a unique identifier in this case.
Next, we will declare a function that will be passed as a prop to the SearchBar component. The function - updateSearchValue() is declared in the App component and then passed as a prop to the SearchBar component.
Now let’s look at the SearchBar component:
function SearchBar({updateSearchValue}) {
return (
<div className="searchbar-div">
<input type="search" id="searchbar" className="search" maxLength={100}
onChange={e => updateSearchValue(e.target.value)} />
<label htmlFor="searchbar"> Search by first name, last name or email address</label>
</div>
);
};
It returns an HTML input element with the onChange function declared inline. As you can see, the onChange function is calling updateSearchValue with the current value of the input as its argument. And looking back at the declaration of updateSearchValue, we can see it is just calling the hook’s setter function “setSearch”. This will update the value of the state “search” attribute to whatever we’ve typed into the SearchBar’s input element.
Great, so now we are sending the value of the SearchBar back to the main App component every time it changes. Next, we need to use that value to refine the list of data we are displaying in our DisplayResults component.
The searchUsers() function uses JavaScript’s Array.prototype.filter() method to narrow down the “results” argument (an array of user objects in this case) based on the value of the searchVal argument, which is the React state value “search”, derived from our SearchBar input element. It checks the search term against the first_name, last_name
and email
properties.
Here’s an example of how the user object data is formatted:
const users =
[{
"id": 1,
"first_name": "Lorene",
"last_name": "Regorz",
"email": "lregorz0@example.com",
"gender": "Male",
"ip_address": "89.44.90.50"
},
And here’s the searchUsers function:
filter the array of users based on the value of the search bar
function searchUsers(results, searchVal) {
let updatedResults = results;
if (searchVal.length > 0) {
updatedResults = [...results].filter(function(r) {
if (r.last_name === null && r.email === null) {
return (r.first_name.toLowerCase().includes(searchVal.toLowerCase()));
}
if (r.last_name === null) {
return (r.first_name.toLowerCase().includes(searchVal.toLowerCase()) ||
r.email.toLowerCase().includes(searchVal.toLowerCase()));
}
if (r.email === null) {
return (r.first_name.toLowerCase().includes(searchVal.toLowerCase()) ||
r.last_name.toLowerCase().includes(searchVal.toLowerCase()));
}
else {
return (r.first_name.toLowerCase().includes(searchVal.toLowerCase()) ||
r.last_name.toLowerCase().includes(searchVal.toLowerCase()) ||
r.email.toLowerCase().includes(searchVal.toLowerCase()));
}
})}
else{
updatedResults = results;
}
return updatedResults;
};
In the searchUsers() function you can see some rudimentary handling of cases where there might be a null value in the text. There are other cases we might like to handle in the future, like what to do with white space, making use of logical “OR”, tokenizing the search term, and so forth. I am leaving the code fairly simple for clarity’s sake, in order to emphasize the interaction between the components and the use of the “useState()” hook.
Finally, we need the DisplayResults function component to render the list of users resulting from our search:
function DisplayResults({last_name, first_name, email}) {
return (
<li>
<p><strong>{last_name}, {first_name}</strong></p>
<p> Email: {email}</p>
</li>
)
};
Put this all together, slap some CSS over it and what you get is this:
When I enter some text into the search bar, the list is updated:
Below is the code, but I also want to acknowledge the following: Dummy Data generated by https://mockaroo.com/
Boilerplate React project generated by create-react-app (specifically, https://create-react-app.dev/docs/getting-started/#creating-a-typescript-app )
import * as React from "react";
import './App.css';
import SearchBar from './SearchBar';
const users =
[{
"id": 1,
"first_name": "Lorene",
"last_name": "Regorz",
"email": "lregorz0@example.com",
"gender": "Male",
"ip_address": "89.44.90.50"
},
// there are 10 users in the screenshot of the UI but I
// just included one to show how the data is formatted
];
// filter the array of users based on the value of the search bar
function searchUsers(results, searchVal) {
let updatedResults = results;
if (searchVal.length > 0) {
updatedResults = [...results].filter(function(r) {
if (r.last_name === null && r.email === null) {
return (r.first_name.toLowerCase().includes(searchVal.toLowerCase()));
}
if (r.last_name === null) {
return (r.first_name.toLowerCase().includes(searchVal.toLowerCase()) ||
r.email.toLowerCase().includes(searchVal.toLowerCase()));
}
if (r.email === null) {
return (r.first_name.toLowerCase().includes(searchVal.toLowerCase()) ||
r.last_name.toLowerCase().includes(searchVal.toLowerCase()));
}
else {
return (r.first_name.toLowerCase().includes(searchVal.toLowerCase()) ||
r.last_name.toLowerCase().includes(searchVal.toLowerCase()) ||
r.email.toLowerCase().includes(searchVal.toLowerCase()));
}
})}
else{
updatedResults = results;
}
return updatedResults;
};
export default function App() {
// the useState hook
const [search, setSearch] = React.useState('');
const updateSearchValue = (search) => {
setSearch(search)
};
const searchResults = searchUsers(users, search);
return (
<div className="App">
<div className="header">
<SearchBar updateSearchValue={updateSearchValue}/>
</div>
<div className="resultsList-div">
<ul className="resultsList">
{searchUsers(users, search).map(r => (
<DisplayResults key = {r.ip_address} {...r} />
))}
</ul>
</div>
</div>
);
};
function SearchBar({updateSearchValue}) {
return (
<div className="searchbar-div">
<input type="search" id="searchbar" className="search" maxLength={100}
onChange={e => updateSearchValue(e.target.value)} />
<label htmlFor="searchbar"> Search by first name, last name or email address</label>
</div>
);
};
function DisplayResults({last_name, first_name, email}) {
return (
<li>
<p><strong>{last_name}, {first_name}</strong></p>
<p> Email: {email}</p>
</li>
)
};
This is a follow-up piece to our recent article on Introduction To React in 2021 which can be viewed here.
The JBS Quick Launch Lab
Free Qualified Assessment
Quantify what it will take to implement your next big idea!
Our assessment session will deliver tangible timelines, costs, high-level requirements, and recommend architectures that will work best. Let JBS prove to you and your team why over 24 years of experience matters.