In this article, we’ll learn how to build a simple web app that lets a user search and display information about their favorite movies using the OMDb API. Obtain an API key by signing up.
In this tutorial, we’ll build a complete frontend app using React. For CSS, we will use Tailwind CSS and Tailwind UI. You can try the finished app here.
To give a brief of how the app will work, the app will allow a user to search for a movie with a name and give the user a list of movies with that name.
PS: This article assumes that you have read and know a bit about React. It is just to give you an idea of a basic way to use React.js — not to teach everything or give you new updates in React.
Now to start, let’s create a React app:
npx create-react-app my-app
This creates a React app. To be sure your project is well set up, run npm start
. You should see this if all works correctly.
Next, let’s include Tailwind CSS and Tailwind UI. Below are the steps to add them.
Install Tailwind and UI. Tailwind comes with its own CLI tool for doing a build, so all we need is the Tailwind package.
npm install tailwindcss npm install @tailwindcss/ui
We’re going to insert a step that builds Tailwind before the existing start and build scripts in package.json
to avoid been ejected from the React app. This will be your script:
"scripts": { "build:tailwind": "tailwindcss build src/tailwind.css -o src/tailwind.output.css", "prestart": "npm run build:tailwind", "prebuild": "npm run build:tailwind", "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }
The build:tailwind
script will compile our src/tailwind.css
file and save it to src/tailwind.output.css
– which our app will then import.
Create a file in src named Tailwind.css
and paste this in it:
@tailwind base; @tailwind components; @tailwind utilities;
Import the CSS file generated.
At the top of your index.js
file, import the CSS file like so:
import React from 'react'; import ReactDOM from 'react-dom'; import './tailwind.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; ReactDOM.render( , document.getElementById('root') ); // 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();
Now, we are done with our setup. Let’s start writing some code.
In this project, we are going to have three components. React allows us to build specific components for each part of our UI. We’ll build a <Header>
component that lets a user input a movie title, and a <Search>
component to display the movie.
App.js
– This will be the parent component for the other 2. It will basically render the other two filesHeader.js
– A simple component that renders the app header and accepts a title propSearch.js
– Contains a form with the search input and also contains functions that handle the input element and resets the field. It also contains a function that calls the search function and displays the moviesLet’s start creating a new folder in the src
directory. We’ll name it components
because that’s where all our components will be. Now let’s create the Header
and Search
components. We’ll then need to import them in our App.js file.
It’ll look like this after:
import React from 'react'; import Header from './Components/Header'; import './tailwind.css'; import SearchMovies from './Components/Search'; import Results from './Components/Results'; function App() { return ( <div className="relative width-full"> <div className="mx-auto overflow-hidden"> <Header/> <SearchMovies/> </div> </div> ); } export default App;
Now in the Header.js
file, add the following code:
import React from 'react'; class Header extends React.Component { render(){ return ( <nav className = "relative mx-auto bg-indigo-700 max-w-7xl py-4 px-4"> <div class="container mx-auto"> <h1 class="text-white text-center text-3xl pb-4"> Movie Search </h1> </div> </nav> ) } } export default Header
This is basically a class component that renders the header view with a title.
Once we have that, the next thing is to work on the Search
component. Let’s add the following code.
First, we’ll create a function that makes an API call to OMDB:
import React, {useState} from 'react'; function SearchMovies(){ const [searching, setSearching] = useState(false); const [message, setMessage] = useState(null); const [query, setQuery] = useState(''); const [movies, setMovies] = useState([]); const searchMovies = async(e) =>{ e.preventDefault(); setSearching(true); const url `http://www.omdbapi.com/?&apikey=e1a73560&s=${query}&type="movie"`; try{ const response = await fetch(url); const data = await response.json(); setMessage(null); setMovies(data.Search); setSearching(false); }catch(err){ setMessage('An unexpected error occured.') setSearching(false); } }
In this component, we have a function called searchMovies
. We then make an API call using async-await
in order to make sure we are getting results from the API. After that, we use the try-catch
block to get the response the API returns. We also used a React Hook called useState
.
As the name states, you can use state and other React features without writing a class. The useState
Hook accepts one argument, which is the initial state, and then it returns an array containing the current state (same as this.state
for class components) and a function to update it.
With ours, we have 4 states. The first being the searching state, which is used to handle the searching process when a search is being made(it renders a loading…
text when searching is set to true
).
The second one is the message state, which renders a message based on the search result (errors) when making the API request. The third is used to set the state which makes sure the input is passed to the API URL. The last is used to handle the movies array that we get from the server.
The function searchMovies
, as it says, is just the input from the user. We pass it to the API URL and make a request to the server to get results.
Now that we’re able to get data from the API, we need to display it in our app for the user. We’ll build a very simple search input that allows a user to type in the name of a movie they like. We’ll query the API for the movie data, and display the response in our UI.
return ( <div className="container mx-auto pt-6"> <div class="flex justify-center max-w-screen-sm mx-auto overflow-hidden px-10"> <form class="w-full h-10 pl-3 pr-2 bg-white border rounded-full flex justify-between items-center relative" onSubmit={searchMovies}> <input type="text" name="query" placeholder="Search movies by name..." class="appearance-none w-full outline-none focus:outline-none active:outline-none" value={query} onChange={(e) =>setQuery(e.target.value)}/> <button type="submit" class="ml-1 outline-none focus:outline-none active:outline-none"> <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-6 h-6"> <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg> </button> </form> </div> <div class="container mx-auto">{searching && !message ? ( <span> loading... </span>): message ? ( <div className = "message"> {message} </div>): (movies.map(movie => ( <div class="inline-block px-2 w-64 h-64"> <div class="bg-white rounded-lg overflow-hidden shadow-xl my-8 py-4"key={movie.imdbID}> <img src={movie.Poster} alt="movieimage" class="w-full h-64"/> <div class="p-4"> <p class="font-medium text-lg">Title: <span class="font-normal text-base leadin-relaxed">{movie.Title}</span></p> <p class="font-medium text-lg">Year of Release: <span class="font-normal text-base">{movie.Year}</span></p></div> </div> </div> )))} </div> </div> )
In the return
section, we have a search input that accepts a name and listens for an event to make a search or call the API.
Then we have the next div
, which iterated through the list of movies returned from the API. It is stored in the movies
variable. We then display details of the movie like the name, year, etc. You can display as many details as you want.
You’ll see that, for some searches, not all results have an image attached to them. So, to make things look good, you’ll have to filter through and display only results with images.
PS: You only do this if you’re going to display the image to the user.
Awesome! Now our app does everything. To summarize, here’s what we did:
We got an API key for the OMDb API. We also built a component that lets a user search for a movie by title, then stores the movie title in that component’s state. Next, we passed the function to the search form so it takes effect when we click the button or hit enter. Then, we stored the response in the Movies
state and built a search component that displays the response data we got from the API. We then show the results to the user.
Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.
LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to balance vibrant visuals with accessible, user-centered options like media queries, syntax, and minimized data use.
Learn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.