DEV Community

Cover image for Implementing Live Search Functionality in JavaScript
OpenReplay Tech Blog
OpenReplay Tech Blog

Posted on • Originally published at blog.openreplay.com

Implementing Live Search Functionality in JavaScript

by Sarah Okolo

In today's dynamic web development realm, implementing powerful search functionality is a key feature that can greatly enhance user experience and make navigating large datasets a breeze. If you want to add live search capabilities to your website or web application, you've come to the right place. This comprehensive article will explore the ins and outs of implementing live search functionality using JavaScript.

Whether you're a seasoned developer or just starting your coding journey, this article is designed to provide you with the general coding knowledge and tools necessary to incorporate live search into your projects. By the end of this guide, you'll have a solid understanding of the concepts and techniques involved, allowing you to create responsive and interactive search functionality that dynamically updates as users type.

To effectively follow along with this guide, it is recommended that you have a solid understanding of HTML, CSS, and JavaScript fundamentals. Familiarity with DOM manipulation and event handling will be beneficial as we dive into the implementation details. However, even if you're relatively new to JavaScript or web development, the guide is structured to provide clear explanations and step-by-step instructions, making it accessible to learners at various skill levels.

Now, to better understand the importance and usage of this functionality, we are going to be creating a very basic project as an example; more specifically, a movie application as shown below:

movie app preview

You can check out the live implementation here.

In this project, we will leverage the live search functionality to search through the list of movies from a movie database. I know you are excited to dive right in; we are getting there. But first, let's get to know a little bit more about what a live search functionality is and its importance.

The Importance of Live Search Functionality

Live search functionality has become vital in today's digital landscape, catering to the need for efficient information retrieval and enhancing the overall user experience. By enabling real-time search updates as users type, live search offers immediate feedback and facilitates quick access to relevant information. This dynamic and interactive search feature brings numerous benefits, benefiting users and website owners.

  1. Improved User Experience: Live search significantly enhances the user experience by providing a seamless and intuitive search process. As users begin typing their query, the search result is updated in real time, offering instant feedback and eliminating the need for manual submissions or page reloads. This interactive nature saves users valuable time and effort, resulting in a more efficient and satisfying search experience.

  2. Faster Information Retrieval: With live search, users can quickly find the desired information without navigating multiple pages or waiting for search results to load. As they type, the search results narrow down instantly, displaying relevant suggestions and eliminating the need to enter the full search query. This speed and responsiveness empower users to find what they're looking for in a fraction of the time it would take with traditional search methods.

  3. Increased Engagement and Conversion: Live search's seamless and responsive nature encourages users to engage more actively with the website or web application. Providing instant feedback and relevant suggestions keeps users engaged, minimizing the likelihood of bounce rates or frustration. This increased engagement can lead to improved conversion rates, as users are more likely to explore the site further and convert their search intent into actions.

  4. Enhanced Filtering and Refinement: Live search functionality often includes additional features like filters, suggestions, and auto-complete options. These features aid users in refining their search and narrowing down the results, allowing them to find what they're looking for. By providing these tools, live search improves the search experience but also assists users in discovering related content or products they may not have initially considered.

  5. Valuable Insights for Website Owners: Live search functionality can provide valuable insights into user behaviour and preferences. By analyzing the search queries and patterns, website owners can better understand what their users are looking for, identify popular trends or topics, and make informed decisions regarding content creation, product offerings, or user interface enhancements. These insights enable website owners to tailor their offerings to better meet user needs, resulting in improved customer satisfaction and business growth.

Setting Up the HTML Structure

Now that we have a full understanding of what a live search functionality is and its importance let’s dive into how you can implement one in your own project as well.

Firstly, let us set up the project’s structure. For this project, we will need just three files, that is, the HTML, CSS, and JavaScript files.

Now let's begin with setting up the HTML structure of our project:
Inside the HTML file, we first need to include our standard HTML boilerplate, including the link and script to our CSS and JS files:

<!doctype html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <link rel="stylesheet" href="./live-search.css" />
   <title> </title>
 </head>
 <body>
   <script src="./live-search.js"></script>
 </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now in the body tag, we include the header and main semantic tags. Inside the header tag is where we set up the header section of our project, which in this case is just the application’s name and a video icon.

<header>
 <ion-icon name="videocam"></ion-icon>
 <h1>Search Movies</h1>
</header>
Enter fullscreen mode Exit fullscreen mode

Before we move on to the main tag, at the end of the body tag, let us include the required script tags in other to be able to make use of the icons:

<script
 type="module"
 src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"
></script>
<script
 nomodule
 src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"
></script>
Enter fullscreen mode Exit fullscreen mode

You can find the icons at the Ionicons website.

Now, inside the main tag, we are going to include our first div tag, and this is going to be our search bar container, and inside it, we place our search input tag and a search icon:

<div id="search-container">
 <ion-icon name="search-outline"></ion-icon>
 <input type="search" id="search-bar" placeholder="Search movies..." />
</div>
Enter fullscreen mode Exit fullscreen mode

Then, we will create another div tag below this' div'. This is going to hold all of the movie data results:

<div id="results-container"></div>
Enter fullscreen mode Exit fullscreen mode

We would leave it empty for now, as its contents will be generated in the JavaScript section.

Lastly, in the main tag, we would include a p tag. This tag is simply to display the error or empty message response to the user later.

<p id="movie-unavailable-txt"></p>
Enter fullscreen mode Exit fullscreen mode

That is all for the HTML file, and the overall code should look like this:

<!doctype html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <link rel="stylesheet" href="./live-search.css" />
   <title>Live Search Functionality</title>
 </head>
 <body>
   <header>
     <ion-icon name="videocam"></ion-icon>
     <h1>Search Movies</h1>
   </header>

   <main>
     <div id="search-container">
       <ion-icon name="search-outline"></ion-icon>
       <input type="search" id="search-bar" placeholder="Search movies..." />
     </div>

     <div id="results-container"></div>

     <p id="movie-unavailable-txt"></p>
   </main>

   <script src="./live-search.js"></script>
   <script
     type="module"
     src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"
   ></script>
   <script
     nomodule
     src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"
   ></script>
 </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now that we are done implementing the project’s HTML structure let us add a few styles to the page.

Adding styles to the page

In this section, we would add basic styling to the various parts of the page. So let's dive right in.

First, let's add some general styles to the overall section of the page:

html{
 scroll-behavior: smooth;
 background-color: #111;
 color: whitesmoke;
}

*{
 margin: 0;
 padding: 0;
 box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

Now let's add some styles to our header section and its contents:

header{
 display: flex;
 justify-content: center;
 padding: 25px;
 letter-spacing: 2px;
 position: sticky;
 top: 0%;
 z-index: 2;
 border-bottom: 2px solid ;
 background-color: black;
 text-shadow: 3px 3px 5px #fd1d6b;   
 box-shadow: 10px 10px 20px -10px #fd1d6b; 
}

header > ion-icon{
 color:#fd1d6b;
 font-size: 60px;
 position: absolute;
 left: 5%;
}
Enter fullscreen mode Exit fullscreen mode

Next, we move on to styling the search container and its contents:

#search-container{
 display: flex;
 justify-content: center;
 padding: 20px;
 margin-bottom: 20px;
 position: sticky;
 top: 100px;
}

#search-bar{
 border: none;
 width: 60%;
 padding: 15px;
 padding-left: 40px;
 border-radius: 15px;
 font-size: 15px;
}

#search-container>ion-icon{
 color: gray;
 position: relative;
 left: 30px;
 top: 13px;
 z-index: 3;
 font-size: 19px;
}
Enter fullscreen mode Exit fullscreen mode

After that, we go ahead to style the results-container that would house all the movies we would be fetching from the movie database later:

#results-container{
 border-right: 5px solid #fd1d6b;
 border-left: 5px solid #fd1d6b;     
 border-radius: 25px;
 display: flex;
 justify-content: center;
 flex-wrap: wrap;
 width: 90vw;
}
Enter fullscreen mode Exit fullscreen mode

Next, we add styles to the movie-unavailable-txt while setting the display to none because we don't want it to be visible just yet:

#movie-unavailable-txt{
 text-align: center;
 letter-spacing: 2px;
 display: none;
 margin-top: 15%;
 text-shadow: 3px 3px 5px #fd1d6b;
}
Enter fullscreen mode Exit fullscreen mode

Next, we will add some styles to a few elements that haven't yet been declared but we would create with javascript. It is the movie card that would display the movie details, housing both the movie image and title:

.movie-cards{
 padding: 25px;
 max-width: 250px;
 border-radius: 15px;
 display: grid;
 place-items: center;
 box-shadow: 1px 1px 20px -1px #fd1d6b ;
 margin: 50px;
}

.title{
 margin: 20px auto;
 text-align: center;
 font-size: 1.2rem;
 text-shadow: 3px 3px 5px #fd1d6b;  
}

.date{
 margin-top: 15px;
 font-size: 0.8rem;
 text-shadow: 3px 3px 5px #fd1d6b;  
}

.movie-image{
 width: 90%;
 max-width: 400px;
 object-fit: contain;
 border-radius: 5px;
}
Enter fullscreen mode Exit fullscreen mode

Now that we are done styling the page let's move on to the most interesting and important section, the Javascript implementation.

Sending Asynchronous Search Requests to Movie Database API.

In this section, we will make API calls to the movie database API of our choice to populate our page with various movies. In this case, I am going to be making use of the IMDb Top 100 Movies free movie API in the RapidAPI Hub. In the API page, we select the particular data you want to make use of and then go ahead to copy the javascript(fetch) code that is provided on the right side of the page for that data, as shown below:
API credentials page

But first off, before you can use the API, subscribe to it first(no credit card required), so an API key can be generated for you. You can do so on the pricing page of the API.

Moving on, we head into our empty javascript file and paste the code we copied in there:

const url = "https://imdb-top-100-movies.p.rapidapi.com/";
const options = {
 method: "GET",
 headers: {
   "X-RapidAPI-Key": "YOUR GENERATED API KEY",
   "X-RapidAPI-Host": "imdb-top-100-movies.p.rapidapi.com",
 },
};

try {
 const response = await fetch(url, options);
 const result = await response.text();
 console.log(result);
} catch (error) {
 console.error(error);
}
Enter fullscreen mode Exit fullscreen mode

Now that we have gotten the API from the movie database into our project, we can use its data. So next up, we are going to declare a few variables that we would need and place them just above the try block in the code we copied:

const searchBar = document.getElementById("search-bar");
const resultsContainer = document.getElementById("results-container");
const movieUnavailableTxt = document.getElementById("movie-unavailable-txt");
let movieList;
let searchValue;
let searchedMovies;
Enter fullscreen mode Exit fullscreen mode

We are getting to the purpose of these variables we just created, hang in there.

Next, we make some changes to the try block from the copied code, as we want to fully integrate it with our project. So first up, we would need to create an asynchronous function:

const fetchMovies = async () => {
 // try catch block goes in here.
};
Enter fullscreen mode Exit fullscreen mode

Inside this function, we will place the entire try catch block from the code we copied, so we can make asynchronous API calls.

Inside the try block, we are going to get rid of the console.log(result) line and change the result variable to the movieList variable we declared previously, and change the response.text() in that same line to response.json(). This is so that the data we received from the API call is presented in a JSON format, which we need. So that line should look like this now:

movieList = await response.json();
Enter fullscreen mode Exit fullscreen mode

Now that we have successfully fetched the movie from the API and returned our JSON dataset, we need to populate that data into our page. For that, we will call the renderMovies() function and set the argument to the data obtained from our API call. Don't worry, we will be creating the function soon:

renderMovies(movieList);
Enter fullscreen mode Exit fullscreen mode

Let us now create the renderMovies function which we just called in the fetchMovies() function, this function would be what we would use to create our dynamic movie card template for which we set the styles to earlier in our CSS file, and in each of the elements in the template, we are going to set their contents to the data we got from the API, that would enable us to render different movies with the same template. We would then place the movie card inside the resultsContainer element. Each time the function is called, we would need to clear the resultsContainer, and also set the moviesUnavailableTxt to display="none" because we want the text to not be visible when we render the movies unto the page, as well as clearing the moviesReturnedOnSearch array, before setting the new data returned from the search input field into it:

const renderMovies = (movies) => {
 resultsContainer.innerHTML = ""; // Clear the existing movies
 movieUnavailableTxt.style.display = "none"; // Hide the "No movies found" message
 moviesReturnedOnSearch = []; // Clear the movies returned on search array

 movies.forEach((movie) => {
   resultsContainer.innerHTML += `
     <div class="movie-cards">
       <img src="${movie.image}" alt="movie image" class="movie-image" />
       <h2 class="title">${movie.title}</h2>
       <p class="plot">${movie.description}</p>
       <p class="date">${movie.year}</p>
     </div>
   `;

   moviesReturnedOnSearch.push(movie); // Add the movies that are a result to the search input value
 });
};
Enter fullscreen mode Exit fullscreen mode

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data.

OpenReplay

Happy debugging! Try using OpenReplay today.


Capturing User Input and Displaying Real-Time Search Results of Movies

Now that we have loaded all the movie data onto our page, here comes the really fun part, where we implement the live search functionality, so, without wasting time, let's dive right in.

To capture the user input, we will be using the input event listener and linking it to the searchBar element. We use this particular event listener because it captures every single activity inside the search box, from typing to clearing to pasting, which is exactly what we want. So let's go ahead and create that:

searchBar.addEventListener("input", (event) => {
 // live functionality code
});
Enter fullscreen mode Exit fullscreen mode

Okay, so now we have linked the input event listener to the search bar to listen to any input from the user. In the second parameter, we have added the event handler, which is the function that will be called whenever a single input is made to the search bar. Now inside that function, we will create the code that handles the live search.

The first thing we need to do inside the search function is to edit the input value gotten from to user and set it to all lowercase as well as get rid of any unnecessary whitespace:

searchValue = event.target.value.trim().toLowerCase();
Enter fullscreen mode Exit fullscreen mode

After that, we proceed to filter the movie on the page by title, based on the user's search input, by checking if the movie title entered by the user includes any of the movie titles in the movieList data while also setting the titles of the movies to lowercase to match the input from the user:

const filteredMovies = movieList.filter( (movie) => movie.title.toLowerCase().includes(searchValue) );
Enter fullscreen mode Exit fullscreen mode

Next, we are going to display the real-time search results of the movies by rendering whatever movie title matches the characters the user has typed in the search bar by calling the renderMovies() function once more and setting the arguments to the value of the filtered Movies variable.

renderMovies(filteredMovies);
Enter fullscreen mode Exit fullscreen mode

By calling this function, it renders unto the page only the movies whose titles matched with whatever characters were typed into the search bar using the movie card template provided in the function while also adding each of the movies that matched into the moviesReturnedOnSearch array, so we can keep track of how many movies matched the search value on each character input. This would be useful when handling the empty response errors, which we would be getting into now.

Handling Empty or Error Responses

In any application, handling empty or error responses effectively is crucial. In this case, these scenarios can occur when the search query does not yield any results or there are issues with the API request.

To handle errors or empty responses, it is essential to provide clear feedback to the user. That said, as this is a pretty light application, we don't have to worry about that much error, as we would only be dealing with errors arising from the API. For instance, the API services could be temporarily down, or the application might have just exceeded its request limit. To handle that error, we just have to set the movieUnavailableTxt element's display to block and set the innerHTML to display the error message to the user and place it inside the catch block of the fetchMovies() function. So now the catch block looks like this:

catch (error) {
 movieUnavailableTxt.innerHTML = 'An error occurred while fetching movies. <br /> Please try again later.';
 movieUnavailableTxt.style.display = "block";
 console.error(error);
}
Enter fullscreen mode Exit fullscreen mode

Now that we are done handling the error response let's move on to handle the empty response. If the movie the user searches for does not match any of the movies on the page, we would have to alert the user that the movie searched for is unavailable. To do so, first, we need to check the content of the moviesReturnedOnSearch array, which we declared earlier, and if the length of the array is less than or equal to 0, we then set the movieUnavailableTxt element's display to block and set the innerHTML to the empty response message we want to display, as shown below:

if (moviesReturnedOnSearch.length <= 0) {
 movieUnavailableTxt.innerHTML = "OOPS! <br/><br/> Movie not available";
 movieUnavailableTxt.style.display = "block"; // Show the "No movies found" message if no movies match the search
}
Enter fullscreen mode Exit fullscreen mode

We will then place this if block just before the closing bracket of the searchBar event handler.

Improving Search Performance with Caching

When implementing live search functionality with an API, one effective technique for enhancing performance is caching. Caching involves storing previously fetched search results and reusing them when the same search query is requested again. This can significantly reduce the number of API calls which would be helpful to prevent exceeding the API's request limit and overall improve the responsiveness of the search feature, as well as the website's load time.

To implement caching in our project, first, we need to identify what item needs to be cached, and in this case, that would be the value of the movieList variable, which is the data we got back from our fetch API request in JSON format. By caching this item, we would be able to make use of the API's data without making a single extra fetch request, even on page reload. But for this project, we will be setting an expiration time for our cached data of 6 hours, meaning the page would only make the API request once every 6 hours rather than on each page reload. This is so the page can keep its data fresh and up to date while maintaining the API request to a minimum.

Heading back into our code, what we need to do now is store the JSON data in the browser's local storage, but to do that, we need to turn it into a string first and set a key name that would be used to identify the data in the local storage. Let's just set it to movieData as shown below:

localStorage.setItem("moviedata", JSON.stringify(movieList));
Enter fullscreen mode Exit fullscreen mode

The next thing we need to do is store the current date and time in the local storage as well:

localStorage.setItem("cacheTimestamp", Date.now());
Enter fullscreen mode Exit fullscreen mode

This stores the current date and time in milliseconds, with the Key name of cacheTimeStamp.

We will place these two lines of code inside the try block of the fetchMovies() function, just right below the movieList variable.

Next, outside the fetchMovies() function, just below the renderMovies() function, we are going to set our cached data's expiration time to 6 hours in milliseconds:

const expirationDuration = 21600000; // 6 hours in milliseconds
Enter fullscreen mode Exit fullscreen mode

After that, we need to get back the cacheTimestamp we set in the local storage earlier:

const cacheTimestamp = localStorage.getItem("cacheTimestamp");
Enter fullscreen mode Exit fullscreen mode

Now, we will check if the cached data has expired or isn't available, meaning it hasn't been stored yet. If that is the case, we will make a new fetch request to the API by calling the fetchMovies() function. On the other hand, if the cached data is present and hasn't expired yet, we will use it to render the movies on the page rather than making a new fetch request again. We do this by retrieving the cached movie data and parsing it back into JSON format to be used, and then calling the render function with the argument set to the data gotten from the cache.

// Check if cache has expired or data is not available
if (
 !cacheTimestamp ||
 Date.now() - parseInt(cacheTimestamp) > expirationDuration
) {
 // Cache expired or data not available, fetch movies again
 fetchMovies();
} else {
 // Use cached movie data
 movieList = JSON.parse(localStorage.getItem("moviedata"));
 renderMovies(movieList);
}
Enter fullscreen mode Exit fullscreen mode

In the if statement, the !cacheTimestamp checks if the cacheTimestamp variable is falsy, meaning it is either null, undefined, 0, false, or an empty string. If cacheTimestamp is false, it indicates no existing cache timestamp is stored. The Date.now() - parseInt(cacheTimestamp) calculates the time difference between the current timestamp and the parsed Integer value of cacheTimestamp. This is simply us saying, "The value of the current time minus the value of the time we previously stored in the cache, is it greater than the expiration time we set? if yes, go ahead to fetch the movies from the API again, and if not, just make use of the cached data."

And there you have it; that is how we cache the data to be reused, rather than making the fetch request on each user input or each page reload. As you can see, this would greatly optimize the application's performance, as it prevents slow movie rendering, which could occur due to a slow network.

We are done implementing all the features in our little movie application that demonstrate how the live search functionality works. Below is the overall javascript code for this application:

const url = "https://imdb-top-100-movies.p.rapidapi.com/";
const options = {
 method: "GET",
 headers: {
   "X-RapidAPI-Key": "Your Generated API Key",
   "X-RapidAPI-Host": "imdb-top-100-movies.p.rapidapi.com",
 },
};

const searchBar = document.getElementById("search-bar");
const resultsContainer = document.getElementById("results-container");
const movieUnavailableTxt = document.getElementById("movie-unavailable-txt");
let movieList;
let searchValue;
let moviesReturnedOnSearch;

// Function to fetch movies from the API
const fetchMovies = async () => {
 try {
   const response = await fetch(url, options);
   movieList = await response.json();

   // Storing the Movie Data in browser storage
   localStorage.setItem("moviedata", JSON.stringify(movieList));
   localStorage.setItem("cacheTimestamp", Date.now()); // Update cache timestamp

   // Render the movies on the page
   renderMovies(movieList);
 } catch (error) {
   movieUnavailableTxt.innerHTML =
     "An error occurred while fetching movies. <br /> Please try again later.";
   movieUnavailableTxt.style.display = "block";
   console.error(error);
 }
};

// Function to render movies on the page
const renderMovies = (movies) => {
 resultsContainer.innerHTML = ""; // Clear the existing movies
 movieUnavailableTxt.style.display = "none"; // Hide the "No movies found" message
 moviesReturnedOnSearch = []; // Clear the movies returned on search array

 movies.forEach((movie) => {
   resultsContainer.innerHTML += `
     <div class="movie-cards">
       <img src="${movie.image}" alt="movie image" class="movie-image" />
       <h2 class="title">${movie.title}</h2>
       <p class="plot">${movie.description}</p>
       <p class="date">${movie.year}</p>
     </div>
   `;

   moviesReturnedOnSearch.push(movie); // Add the movies that are a result to the search input value
 });
};

const cacheTimestamp = localStorage.getItem("cacheTimestamp");
const expirationDuration = 21600000; // 6 hours in milliseconds

// Check if cache has expired or data is not available
if (
 !cacheTimestamp ||
 Date.now() - parseInt(cacheTimestamp) > expirationDuration
) {
 // Cache expired or data not available, fetch movies again
 fetchMovies();
} else {
 // Use cached movie data
 movieList = JSON.parse(localStorage.getItem("moviedata"));
 renderMovies(movieList);
}

// Event listener and handler for search bar input
searchBar.addEventListener("input", (event) => {
 searchValue = event.target.value.trim().toLowerCase();

 // Filter movies based on search input
 const filteredMovies = movieList.filter((movie) =>
   movie.title.toLowerCase().includes(searchValue),
 );

 // Render the filtered movies on the page
 renderMovies(filteredMovies);

 if (moviesReturnedOnSearch.length <= 0) {
   movieUnavailableTxt.style.display = "block"; // Show the "No movies found" message if no movies match the search
 }
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this guide, we have explored the implementation of live search functionality in JavaScript using an API. By following the steps outlined, you can create a dynamic search experience that provides real-time results as users type in the search bar.

By implementing live search functionality to your website, you can enhance user engagement and improve the usability of your website or application. Users will appreciate the ability to find relevant information quickly and conveniently, all without the need for page reloads.

With the knowledge gained from this guide, you are well-equipped to implement live search functionality in JavaScript effectively. Embrace the power of dynamic searching and create a seamless user experience that will leave a lasting impression.

Happy coding!

Top comments (0)