(Photo by Markus Winkler on Unsplash)

Looking to add search functionality to your React application but not entirely sure how to implement it. Well, in this article I provide a detailed step by step on how to implement search with React Hooks

Below is a look at the finished application.

Gif of finished application

In case you just want to look at the finished code. At the end of this article, there is a link to the code on GitHub.

We are going to create a simple application that grabs a search value from the user then makes an API call to one of the TheMealDB APIs, then displays the results. Below is the URL we shall use to make the query.

https://www.themealdb.com/api/json/v1/1/search.php?s=Arrabiata

Let's get started by creating a new React project using create-react-app. We are going to use React with Typescript. From your terminal/command prompt, navigate to where you want to create your project and enter the command below:

npx create-react-app meals --template typescript

This will give you a fresh clean react project. I called my project meals. Feel free to give your project any name. Open the project folder with cd meals . You should have a project structure similar to the one below

Exploring the files and folders.

  1. node_modules. This folder contains the JavaScript packages (directories) that our app is using. When you install node packages. The directories of these packages get placed in this folder
  2. public. This folder contains files that are accessible to the public, this is the folder the server looks into when displaying web pages of our app.
  3. favicon.ico. Favicon is a website icon that appears on the left-hand side of the tab in which a web page is open just next to the title of the website.
  4. index.html. This is the homepage (default page) of the application. All our react components will be rendered through this page.
  5. manifest.json this file just contains some information about our react app.
  6. src. This folder will contain all of the source code that we are going to write.
  7. App.css. This file contains css for the App.js component.
  8. App.tsx. This file contains a simple basic component that renders when we first run our  app.
  9. App.test.tsx. This file contains a simple unit test for the basic App.tsx component.
  10. index.ts. This typescript file is the entry to our application. This is the file where we plug our components that plug it into the root divin the index.html
  11. logo.svg. This is the React logo that gets displayed when we run our application.
  12. react-app-env.d.ts This file references the types of react-scripts
  13. serviceWorker.ts. This is a file that is automatically generated by the create-react-app. This file basically serves assets from the local cache when in a production environment.
  14. setupTest.ts This file allows you to do some configurations for the test libraries you are going to use.
  15. .gitignore. create-react-app automatically generates for us this .gitignore file. Just as the name sounds this file is used to ignore files and folders you don't want Git to track.
  16. package.json. This file contains some metadata about our application. Most importantly it contains a list of dependencies with their versions that our application is using. It would be the equivalent of the requirements.txt file in python.
  17. README.md. This is a file that is displayed on the GitHub repository of your application. Just as the name sounds, this file should contain information that people visiting your application’s repository can read to inform them about your application.
  18. tsconfig.json This is the TypeScript configuration file, which has some defaults set. You can add or edit any of the Typescript configurations for your app in this file.
  19. yarn.lock This file contains more metadata about the javascript packages and their versions that your app is using.

Running the app

You can run the app by running the yarn start command in your terminal. This will automatically open your default browser with the app running on localhost on port 3000.

Editing project files and creating search functionality.

We are going to delete a few files we will not be needing. So delete App.css, App.tsx, index.css, logo.svg,  App.test.ts and serviceWorker.js.

In the src/index.ts file, we need t0 delete some lines of code.

import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

// ....
<App />
// ...

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

In the src folder, create a new file and call it Search.tsx Add the code below to this file.

import React from 'react';

export default function Search() {
  return (
    <div>
      <h1>Meals</h1>
      <input name="text" type="text" placeholder="Search for a meal" />
      <button>Search</button>
    </div>
  );
}

Import this Search component in the index.ts file and render it. This is how the index.ts file looks like now

import React from 'react';
import ReactDOM from 'react-dom';

import Search from './Search';

ReactDOM.render(
  <React.StrictMode>
    <Search />
  </React.StrictMode>,
  document.getElementById('root')
);

We now have an input and a button we can use to perform the search, let us style it a little bit before we go ahead. In the src folder, create a css file, you can call it anything, I will call mine Search.css Add the code below to your css file.

* {
  margin: 0;
  padding: 0;
}
body {
  background: #fff;
  color: #222;
}
h1 {
  color: #4286f4;
  margin-top: 30px;
  margin-bottom: 60px;
}
div {
  text-align: center;
  width: 80%;
  margin: 0 auto;
}
input {
  width: 100%;
  margin: 10px;
  padding: 18px;
  border-radius: 22px;
  border: 1px solid #dfe1e5;
  box-shadow: none;
  font-size: 16px;
  outline: none;
}
button {
  padding: 12px 10px;
  width: 30%;
  border-radius: 4px;
  background-color: #4286f4;
  color: #ffffff;
  cursor: pointer;
  font-size: 18px;
  border: none;
  outline: none;
}

Make sure to import this css file in your component. You can import it with import './Search.css'

With an interface in place to perform the search, let us power it up. Using the useState hook, we are going to create a variable that can hold what the user enters in the search input. Import useState from react: import React, {useState} from 'react';

Create a state value to hold the user input like so:

const [searchValue, setSearchValue] = useState('');

Next, we are going to change our input to a controlled component by adding the value attribute and an onChange listener.

Set the onChange to be handled by a function handleOnChange which takes in event as a parameter. event is an object that is created when the user types something in the input element.

Create the handleOnChange function inside your functional component:

const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
   setSearchValue(event.target.value);
};

We now have the search value, let us perform the search. To do this we need to first add an onClick attribute to the Search Button.

<button onClick={handleSearch}>Search</button>

Define handleSearch just below handleOnChange within the functional component still.

const handleSearch = () => {};

Alright, now that we have a function to handle the search, let us make that API call. I am going to use the Fetch API for this. Below the handleSearch function, create a new function to make the API call:

const makeApiCall = (name: string) => {
    var searchUrl = `https://www.themealdb.com/api/json/v1/1/search.php?s=${name}`;
    fetch(searchUrl)
    .then((response) => {
    return response.json();
    })
    .then((jsonData) => {
    });
};

Call this newly created function, makeApiCall in the handleSearch function with the searchValue

const handleSearch = () => {
  makeApiCall(searchValue);
};

We are now able to fetch the meals that contain the letters the user entered in the search field. Next, let us display the results

Displaying Search Results

Create another state variable to hold the results. I will call mine meals and will initialize it to an empty array.

const [meals, setMeals] = useState([]);

Now back in our makeApiCall function, let’s set the results array being returned to the meals array. So in the body of the last .then, add this line

setMeals(jsonData.meals);

Let us render the contents of this array below the button element in the render function, add this code:

{meals ? (
  <div>
    {meals.map((meal : Meal, index) => (
      <div key={index}>
        <h1>{meal.strMeal}</h1>
        <img src={meal.strMealThumb} alt="meal-thumbnail" />
      </div>
    ))}
  </div>
) : (
  <p>Try searching for a different meal</p>
)}

Notice how my meal is of type Meal , I created an interface to help define this type. You can also create one, outside of your functional component like so:

export interface Meal {
  strMeal: string;
  strMealThumb?: string;
}

That is it, you should now be able to perform the search and display the results.

Let us however improve on this app a little bit before we leave.

Improving Application User Experience

First we shall add a loader to give a user feedback when they start searching that they are searching.

In the src folder, create a Loader.tsx file, add the code below to it:

import React from 'react';

import './Loader.css';

export const Loader = () => (
  <div className="Loader ">
    <div />
    <div />
    <div />
  </div>
);

export default Loader;

Again, in the src folder, create the corresponding css file, add the following code to this css file:

.Loader {
  display: inline-block;
  position: relative;
  width: 64px;
  height: 40px;
}
.Loader div {
  display: inline-block;
  position: absolute;
  left: 6px;
  width: 8px;
  background: blue;
  animation: loader 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite;
}
.Loader div:nth-child(1) {
  left: 6px;
  animation-delay: -0.24s;
}
.Loader div:nth-child(2) {
  left: 26px;
  animation-delay: -0.12s;
}
.Loader div:nth-child(3) {
  left: 45px;
  animation-delay: 0;
}
@keyframes loader {
  0% {
    top: 6px;
    height: 30px;
  }
  50%,
  100% {
    top: 19px;
    height: 20px;
  }
}

Import the Loader in the search.tsx file. import Loader from './Loader'; . Then create a state variable to store the loading state. I will call mine loading.

const [loading, setLoading] = useState(false);

In the makeApiCall function, before fetch, set loading to true: setLoading(true) . Still in the makeApiCall function, after setMeals(jsonData.meals), set loading to false: setLoading(false)

Just below button in the return, add:

{loading && (
  <div>
    <Loader />
  </div>
)}

Lastly, let us style the results a bit.

Add a class name to the div that contains the meals, className="Meals-container". Also add a class name to the div that contains a single meal. className="Meal" . Finally add the following css code in the Search.css file.

.Meals-container {
  display: grid;
  grid-template-columns: auto auto auto;
  grid-column-gap: 10px;
  grid-row-gap: 15px;
  margin-top: 60px;
}

.Meal {
  margin: 10px;
  border: 2px solid lightgray;
  border-radius: 4px;
}
.Meal img {
  width: 100%;
}

Alright, there you have it, a simple search app. You can find the code on Github here.

See you on the next series.

Happy Coding

You've successfully subscribed to Decoded For Devs
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Your link has expired
Success! Your account is fully activated, you now have access to all content.