Design and Develop a Functional Search Bar in React

Andrew Lukes
11 min readJan 29, 2024

--

Simple Follow-along Tutorial for full-stack Developers

Are you looking to enhance user experience on your website or web application? One effective solution is to design and develop a functional search bar. A search bar allows users to quickly and easily find the information they need, improving website navigation and overall usability. In this blog post, we will delve into the importance of a search bar and guide you through the process of setting up the environment, designing the code, and deploying the search bar in your React application.

Photo by Nathana Rebouças on Unsplash

Why is a search bar useful?

A search bar offers several benefits for users and website owners alike. Firstly, it enables users to find specific content or products quickly, saving them time and effort. Imagine a scenario where a user wants to find a particular article or product on your website. Without a search bar, they would have to navigate through various pages, which can be frustrating. With a search bar, users can simply enter keywords and find what they’re looking for instantly.

Additionally, a search bar enhances the overall user experience by improving website navigation. Users have become accustomed to the convenience of search bars on popular platforms like Google and Amazon. By providing a search bar on your website, you create a familiar and intuitive experience for your users.

Lastly, a search bar can also provide valuable insights into user behavior and preferences. By analyzing search queries, you can gain a deeper understanding of what your users are looking for and optimize your website or application accordingly.

Setting up the environment

Requirements

Before diving into the code, it’s crucial to set up the development environment properly. To implement a search bar in React, you’ll need to have Node.js and PostgreSQL. This tutorial will assume, that you have at least basic knowledge of all three technologies. Where relevant, I will try to add links and guidance for setting their respective environments.

Node.js will serve as the backend to handle the search functionality, while PostgreSQL will store the data to be searched. React will be used to build the frontend components and facilitate the user interface.

Here is a simple diagram of our application:

How to set up Node.js

Get Node.js-> here

YouTube tutorial: (Made By ProgrammingKnowledge2 )

How to set up PostgreSQL

Official documentation -> here

YouTube tutorial: (Made by Geeky Script)

Folder Structure

Create two folders. ‘client’ for frontend the frontend part and ‘server’ for the backend.

In the client folder open the terminal and type in these commands one after the other:

npx create-react-app@latest autocomplete  
cd autocomplete
npm i axios
npm run start

This will create a new folder with a react application and an Axios dependency, which will be used to make requests to the backend.

In the server folder first create a file named app.js. Then run the following commands:

npm init -y

Into the created package.json, copy this object. After that, enter the two commands below.

{
"name": "sever",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon dist/app.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.6.5",
"cors": "^2.8.5",
"express": "^4.18.2",
"nodemon": "^3.0.3",
"pg": "^8.11.3"
},

}
npm i 
npm run dev

Your folder structure should look something like this

Designing Frontend

Once the environment is set up, it’s time to design the code for the search bar. This section will walk you through a step-by-step process of developing the search functionality with React and Node.js. We’ll also explore different approaches, such as exact match searching or fuzzy searching, to make the search bar more versatile and user-friendly.

Redesigning the app

First, we will replace the default code inside the React application with the one below.

import {useState} from 'react'

function App() {
const [query, setQuery] = useState ("");
const [results, setResults] = useState ([]);
return (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100vh",
flexDirection: "column",
}}
>
<div style={{
position: "absolute",
top: "30%",
left: "50%",
transform: "translate(-50% )",
fontSize: '2em'
}}>
<span style={ {color: "#4285F4"}}>G</span>
<span style={{color: "#EA4335"}}>o</span>
<span style={{color: "#FBBC05"}}>o</span>
<span style={{color: "#4285F4"}}>g</span>
<span style={{color: "#34A853"}}>l</span>
<span style={{color: "#EA4335"}}>e</span>
<span style={{color: "#FBBC05"}}>p</span>
<span style={{color: "#34A853"}}>l</span>
<span style={{color: "#4285F4"}}>e</span>
<span style={{color: "#EA4335"}}>x</span>
</div>
<div
style={{
position: "absolute",
top: "40%",
left: "50%",
transform: "translate(-50% )",
}}
>
{/*<AutocompleteSearchBar query={query} setQuery={setQuery}/>*/}
{/* <QueryListTable results={results} query={query} setQuery={setQuery}/> We will add these components later*/}
</div>
</div>
);
}
export default App;

Designing the search bar

We will create the search bar in a new file. Add the code below. Then download a looking glass SVG here add it to the project and import it to the component.

import searchLogo from './search-svgrepo-com.svg'
const AutocompleteSearchBar = ({query, setQuery}) => {
return (
<div
style={{
padding: "12px",
borderRadius: "8px",
border: "1px solid #ccc",
width: "300px",
display: "flex",
justifyContent: "start",
gap: "0.5em",
alignItems: "center",
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.2)", // Add this line for box shadow
}}
>

<img src={searchLogo} alt="looking-glass" height={16} width={16} />
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
style={{
width: "100%",
border: 'none',
zIndex: "1",
}}
/>
</div>
);
}
export default AutocompleteSearchBar
// DONT FORGET TO UNCOMMENT THe FOLLOWING LINE IN App.js! <AutocompleteSearchBar query={query} setQuery={setQuery}/>

To remove the outline around the input field when typing add this to the index.css:

input:focus {
outline: none;
}

Your search bar should look like this

Creating Query Whisperer

Next, we will create the query whisperer under your search bar. Create two new files and create these two components

import OneQuery from "./OneQuery";
const QueryListTable = ({results, query, setQuery}) => {
const [shownResults, setShownResults] = useState ([]);

useEffect(() => {
let newShownResults = [];
results.forEach((result) => {
// this code will find the overlap between the query and
// the result and split it based on it
const fullquery = result.fullquery.replace(/\s+/g, " ");
const queryWithoutSpaces = query.replace(/\s+/g, " ");
const indexOfQuery = fullquery
.toLocaleLowerCase()
.indexOf(queryWithoutSpaces.toLocaleLowerCase());
const beforeQuery = fullquery.slice(0, indexOfQuery);
const afterQuery = fullquery.slice(
indexOfQuery + queryWithoutSpaces.length
);
// only show the result if the overlap actually exists
if( indexOfQuery >= 0 ){
newShownResults.push({
boldedPartBefore: beforeQuery,
normalText: queryWithoutSpaces,
boldedPartAfter: afterQuery,
popularity: result.popularity,
});
}
});
// Sort the newShownQueries array based on popularity in descending order
newShownResults.sort((a, b) => b.popularity - a.popularity);
setShownResults(newShownResults);
}, [query, results]);

return (
<>
{shownResults.length > 0 && query && (
<table
style={{
width: "100%",
zIndex: -1,
backgroundColor: "#f7f6f2",
padding: "0.25em",
maxHeight: "500px",
}}
>
<tbody>
{shownResults.map((shownQuery, key) => {
return (
<OneQuery
key={key}
boldedPartBefore={shownQuery.boldedPartBefore}
normalText={shownQuery.normalText}
boldedPartAfter={shownQuery.boldedPartAfter}
query={query}
setQuery={setQuery}
/>
);
})}
</tbody>
</table>
)}
</>
);
};
export default QueryListTable
const OneQuery = ({ boldedPartBefore, normalText, boldedPartAfter , query, setQuery }) => {
// when clicking on the autcompleted query, it will set the query to the value
// of this component
const handleClick = () => {
setQuery(`${boldedPartBefore}${normalText}${boldedPartAfter}`);
};

return (
<tr onClick={handleClick}>
<td
style={{
maxWidth: "300px",
height: "auto",
wordWrap: "break-word",
textAlign: "start",
cursor: 'pointer'
}}
>
<span style={{ fontWeight: "bold" }}>{boldedPartBefore}</span>{normalText}<span style={{ fontWeight: "bold" }}>{boldedPartAfter}</span>
</td>
</tr>
);
};

export default OneQuery;

Adding Mock Data

you can test the search bar by adding this to the results useState in ‘App.js’.

[
{
"id": 1,
"keyword": "demand-driven",
"popularity": 98,
"fullquery": "demand-driven massa id lobortis"
},
{
"id": 2,
"keyword": "5th generation",
"popularity": 76,
"fullquery": "5th generation porta volutpat quam"
},
{
"id": 3,
"keyword": "holistic",
"popularity": 30,
"fullquery": "holistic mi integer ac neque"
},
{
"id": 4,
"keyword": "Exclusive",
"popularity": 2,
"fullquery": "Exclusive fermentum donec"
},
{
"id": 5,
"keyword": "neutral",
"popularity": 82,
"fullquery": "neutral amet justo morbi ut odio"
},
{
"id": 6,
"keyword": "Multi-layered",
"popularity": 64,
"fullquery": "Multi-layered eget tincidunt eget tempus vel"
}
]

Designing Backend

Node.Js will handle parsing and sending data from our database as JSON.

In server/app.js add this code to create a basic server application

const express = require('express');
const { Pool } = require('pg');
const cors = require('cors');

const app = express();
app.use(cors());
const port = 3002;
// the object bellow replaces an answer from the PostgreSQL db
const mockData = {rows:[
{
"id": 1,
"keyword": "demand-driven",
"popularity": 98,
"fullquery": "demand-driven massa id lobortis"
},
{
"id": 2,
"keyword": "5th generation",
"popularity": 76,
"fullquery": "5th generation porta volutpat quam"
},
{
"id": 3,
"keyword": "holistic",
"popularity": 30,
"fullquery": "holistic mi integer ac neque"
},
{
"id": 4,
"keyword": "Exclusive",
"popularity": 2,
"fullquery": "Exclusive fermentum donec"
},
{
"id": 5,
"keyword": "neutral",
"popularity": 82,
"fullquery": "neutral amet justo morbi ut odio"
},
{
"id": 6,
"keyword": "Multi-layered",
"popularity": 64,
"fullquery": "Multi-layered eget tincidunt eget tempus vel"
}
]}

app.get('/search', async (req , res ) => {
const { query } = req.query;
if (!query || query?.trim() == ''){
res.json(null)
return
}
try {
const result = mockData // we will get the data from the database later on
res.json(result.rows);
} catch (error) {
console.error('Error executing search query:', error);
res.status(500).json({ error: 'Internal Server Error' });
}
});


app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});

Retrieving data from the database

We will sign in to our PSQL and set up a new database. After that, we will create a table with some search results through the terminal. You can get some random data here.

Here is what the response from the terminal will look like after entering the data:

After you have set up the table with the data you can extract the queries from it. First, comment out the mock data. Then include the code below.

const pool = new Pool({
host: 'localhost',
user: 'postgres',
database: 'your db',
password: 'your password',
port: 5432,
});
app.get('/search', async (req , res ) => {
// code before
const result = await pool.query(
`SELECT * FROM queries `,
);
// code after
})

Retrieving Data On the Frontend

Go back to your react application and add this effect to your ‘App.js’. It will fetch the data every time you change the query.

  useEffect(() => {
axios
.get("http://localhost:3002/search", { params: { query } })
.then((response) => {
const data = response.data;
if (data === null){
return
}
setResults(data);

})
.catch((error) => {
throw new Error('problem with fetching data')
});
}, [query]);

// make sure to import axios into App.js -> import axios from 'axios'

This is what should appear in your search bar when typing.

Filtering Data

To work with more relevant results add these functions to the app.js file.

// checks whether a keyword is contained in the query
const checkSearchRelevant = (keyword , query ) => {
if (query.replace(/\s/g, "") === ''){
return false
}
const keywordWithoutSpaces = keyword.replace(/\s+/g, " ").trim();
const queryWithoutSpaces = query.replace(/\s+/g, " ").trim();
return (
keywordWithoutSpaces.toLocaleLowerCase().indexOf(queryWithoutSpaces.toLocaleLowerCase()) >= 0 || queryWithoutSpaces.toLocaleLowerCase().indexOf(keywordWithoutSpaces.toLocaleLowerCase()) >= 0
);
};
// self explanatory
const checkResultStartWithQuery = (result , query ) => {
return result.replace(/\s+/g, " ").trim().toLocaleLowerCase().indexOf( query.replace(/\s+/g, " ").trim().toLocaleLowerCase()) == 0
}
// gets how similar one string is to another
function getSimilarity (result , query ) {
result = result.toLowerCase()
query = query.toLowerCase()

return result.length - result.replace(new RegExp(query, 'g'), '').length
}
// checks whether the word is misspelled
// note that to show results passed through this function in the front end you
// should add to the check in the querylist table for the index of query
function levenshteinDistance(word1, word2) {
const m = word1.length;
const n = word2.length;

// Create a 2D matrix to store the distances
const dp = Array(m + 1)
.fill(null)
.map(() => Array(n + 1).fill(0));

// Initialize the first row and column of the matrix
for (let i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 0; j <= n; j++) {
dp[0][j] = j;
}

// Calculate the distances
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(
dp[i - 1][j] + 1, // Deletion
dp[i][j - 1] + 1, // Insertion
dp[i - 1][j - 1] + 1 // Substitution
);
}
}
}

// Return the distance between the two words
return dp[m][n];
}

Rewrite the get request as follows:

app.get('/search', async (req  , res ) => {
const { query } = req.query;
if (!query){
res.json(null)
return
}
try {
const result = await pool.query(
`SELECT * FROM queries `,
); // we will get the data from the database later on
const filteredResults = result.rows.filter((result) => {
return checkSearchRelevant(result.keyword, query ) || checkResultStartWithQuery(result.fullquery, query ) || levenshteinDistance(result.keyword, query) < 2;
});

filteredResults.sort((a, b) => {
return getSimilarity(b.keyword, query ) - getSimilarity(a.keyword, query )
})
res.json(filteredResults);
} catch (error) {
console.error('Error executing search query:', error);
res.status(500).json({ error: 'Internal Server Error' });
}
});

End Result

If you followed correctly, here is what the result should look like now:

search bar in action

You can see the project on Github

Conclusion

In conclusion, a functional search bar is a powerful tool that can significantly enhance the user experience, improve website navigation, and provide valuable insights into user behavior. By following the steps outlined in this blog post, you can design and develop a search bar in React that will take your website or application to the next level.

Don’t underestimate the importance of a search bar. Implementing this feature will not only benefit your users but also contribute to the success of your website or application. So, start today and empower your users with a seamless search experience!

About the Author

My name is Andrew. I have been coding for the past 2 years. I am primarily focused on coding in JavaScript/React with some knowledge of Python and GD script for game development. I have already finished projects like a full-stack web app for my school magazine a chess clone or a top-down randomly generated game. In the future, I want to create easy-to-use web applications utilizing the power of AI and prompt engineering.

Also Read

Photo by Yuyang Liu on Unsplash

--

--

Andrew Lukes

Introducing Andrew Lukes: a Prague web dev & language enthusiast who shares his ideas on Medium. You can visit andrewebdev.online to see some of my projects