search
HomeWeb Front-endCSS TutorialBuilding a Movie Finder Website using React

Building a Movie Finder Website using React

Introduction

In this blog, we will walk through the process of building a Movie Finder website using React and the OMDB API. The website allows users to browse movies by categories like Avengers, Star Wars, and Series, and search for movies using specific queries. Each movie has its detail page, making it easy to explore more about your favorite films.

Project Overview

Movie Finder Website enables users to:

  • Browse categories like Avengers and Star Wars.
  • Search for movies by keywords.
  • View detailed movie information (poster, genre, director, actors, and more).
  • Navigate easily through the website with a clean, modern design.

Features

  • Fetch data dynamically using the OMDB API.
  • Responsive design for a better user experience.
  • Search functionality that provides instant results.
  • Loading indicators while data is being fetched.
  • View details of individual movies on a separate page.

Technologies Used

  • React: Frontend library for building UI components.
  • React Router: For navigation and routing.
  • Axios: For making HTTP requests to the OMDB API.
  • OMDB API: To fetch movie details.
  • CSS: For styling the application.

Project Structure

Here is the directory structure for the project:

movie-finder/
├── public/
├── src/
│   ├── components/
│   │   └── Navbar.js
│   │   └── Footer.js
│   ├── pages/
│   │   └── Home.js
│   │   └── Movies.js
│   │   └── Series.js
│   │   └── SearchResults.js
│   │   └── MovieDetail.js
│   └── App.js
│   └── App.css
└── package.json

Installation

  1. Clone the repository:

    git clone https://github.com/abhishekgurjar-in/movie-finder.git
    cd movie-finder
    
  2. Install dependencies:

    npm install
    
  3. Get your API key from OMDB API.

  4. Create a .env file in the project root and add your API key:

    REACT_APP_OMDB_API_KEY=yourapikey
    
  5. Run the project:

    npm start
    

Usage

1. Home Page

The Home Page showcases two categories of movies: Avengers and Star Wars. When the user clicks on a movie card, they are redirected to the detailed movie page.

Code Snippet from Home.js:

import React, { useEffect, useState } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";
import Movies from "./Movies";
import Series from "./Series";

const Home = () => {
  const [avengersMovies, setAvengersMovies] = useState([]);
  const [starWarsMovies, setStarWarsMovies] = useState([]);
  const [loadingAvengers, setLoadingAvengers] = useState(true);
  const [loadingStarWars, setLoadingStarWars] = useState(true);
  const navigate = useNavigate();

  useEffect(() => {
    fetchMovies("Avengers", setAvengersMovies, setLoadingAvengers);
    fetchMovies("Star Wars", setStarWarsMovies, setLoadingStarWars);
  }, []);

  const fetchMovies = (query, setMovies, setLoading) => {
    axios
      .get(`http://www.omdbapi.com/?s=${query}&apikey=you_api_key`)
      .then((response) => {
        if (response.data.Search) {
          setMovies(response.data.Search);
        } else {
          setMovies([]); // Clear movies if no results
        }
      })
      .catch((error) => {
        console.error(`There was an error fetching the ${query} movies!`, error);
        setMovies([]); // Clear movies if there is an error
      })
      .finally(() => {
        setLoading(false);
      });
  };

  const handleCardClick = (id) => {
    navigate(`/movie/${id}`);
  };

  const renderMovies = (movies, loading) => (
    loading ? (
      <div classname="loader"><div classname="load"></div></div>
    ) : (
      <div classname="cards">
        {movies.length > 0 ? (
          movies.map((movie) => (
            <div key="{movie.imdbID}" classname="card" onclick="{()"> handleCardClick(movie.imdbID)}>
              <img src="%7Bmovie.Poster%7D" alt="{movie.Title}">
              <h2 id="movie-Title">{movie.Title}</h2>
            </div>
          ))
        ) : (
          <p>No movies found.</p>
        )}
      </div>
    )
  );

  return (
    
      <div classname="home">
        <div classname="movie-category">
          <h4 id="Avengers-Movies">Avengers Movies</h4>
          {renderMovies(avengersMovies, loadingAvengers)}
        </div>
        <br>
        <br>
        <div classname="movie-category">
          <h4 id="Star-Wars-Movies">Star Wars Movies</h4>
          {renderMovies(starWarsMovies, loadingStarWars)}
        </div>
      </div>
      <movies></movies>
      <series></series>
    >
  );
};

export default Home;

2. Search Functionality

The user can search for any movie using the search bar at the top of the website. Search results are fetched from the OMDB API based on the user’s query.

Code Snippet from SearchResults.js:

import React, { useEffect, useState } from "react";
import axios from "axios";
import { useParams, useNavigate } from "react-router-dom";

const SearchResults = () => {
  const [movies, setMovies] = useState([]);
  const [loading, setLoading] = useState(false);
  const { query } = useParams();
  const navigate = useNavigate(); // Add this line to use navigate

  useEffect(() => {
    if (query) {
      setLoading(true);  // Set loading to true before starting the fetch
      axios
        .get(`http://www.omdbapi.com/?s=${query}&apikey=your_api_key`)
        .then((response) => {
          if (response.data.Search) {
            setMovies(response.data.Search);
          } else {
            setMovies([]); // Clear movies if no results
          }
        })
        .catch((error) => {
          console.error("There was an error fetching the movie data!", error);
        })
        .finally(() => {
          setLoading(false);  // Set loading to false once fetch is complete
        });
    }
  }, [query]);

  const handleCardClick = (id) => {
    navigate(`/movie/${id}`); // Navigate to movie detail page
  };

  return (
    <div classname="search-results">
      <h4 id="Search-Results-for-query">Search Results for "{query}"</h4>
      {loading ? (
        <div classname="loader"><div classname="load"></div></div>  // Loader
      ) : (
        <div classname="cards">
          {movies.length > 0 ? (
            movies.map((movie) => (
              <div key="{movie.imdbID}" classname="card" onclick="{()"> handleCardClick(movie.imdbID)}>
                <img src="%7Bmovie.Poster%7D" alt="{movie.Title}">
                <h2 id="movie-Title">{movie.Title}</h2>
              </div>
            ))
          ) : (
            <p>No results found.</p>
          )}
        </div>
      )}
    </div>
  );
};

export default SearchResults;

3. Movie Detail Page

When a user clicks on a movie, they are redirected to the movie detail page. This page shows the movie’s title, poster, plot, actors, and more.

Code Snippet from MovieDetail.js:

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';

const MovieDetail = () => {
  const [movie, setMovie] = useState(null);
  const [loading, setLoading] = useState(true);
  const { id } = useParams();

  useEffect(() => {
    axios.get(`http://www.omdbapi.com/?i=${id}&apikey=your_api_key`)
      .then((response) => {
        setMovie(response.data);
      })
      .catch((error) => {
        console.error("There was an error fetching the movie details!", error);
      })
      .finally(() => {
        setLoading(false);
      });
  }, [id]);

  if (loading) return <div classname="loader">
    <div classname="load"></div>
  </div>;
  if (!movie) return <div classname="loader">No movie data found!</div>;

  return (
    <div classname="movie-detail">

<div classname="detail-box">
<h1 id="movie-Title">{movie.Title}</h1>
<p><strong>Year:</strong> {movie.Year}</p>
      <p><strong>Rating:</strong> {movie.imdbRating}</p>
      <p><strong>Genre:</strong> {movie.Genre}</p>
      <p><strong>Director:</strong> {movie.Director}</p>
      <p><strong>Actors:</strong> {movie.Actors}</p>
      <p><strong>Plot:</strong> {movie.Plot}</p>
      <p><strong>Runtime:</strong> {movie.Runtime}</p>
      <p><strong>Language:</strong> {movie.Language}</p>
      <p><strong>Country:</strong> {movie.Country}</p>
      <p><strong>Awards:</strong> {movie.Awards}</p>
</div>
    <div classname="img-box">
    <img src="%7Bmovie.Poster%7D" alt="{movie.Title}">
    </div>
    </div>
  );
};

export default MovieDetail;

4. Movies and Series Pages

Movies.js and Series.js pages list movies and TV series, respectively.

Code Snippet from Movies.js:

import React, { useEffect, useState } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";

const Movies = () => {
  const [movies, setMovies] = useState([]);
  const navigate = useNavigate();

  useEffect(() => {
    axios
      .get(`http://www.omdbapi.com/?s=Avengers&type=movie&apikey=${process.env.REACT_APP_OMDB_API_KEY}`)
      .then(response => setMovies(response.data.Search || []))
      .catch(error => console.error(error));
  }, []);

  const handleCardClick = (id) => {
    navigate(`/detail/${id}`);
  };

  return (
    <div classname="movies">
      <h2 id="Movies">Movies</h2>
      <div classname="cards">
        {movies.map(movie => (
          <div key="{movie.imdbID}" classname="card" onclick="{()"> handleCardClick(movie.imdbID)}>
            <img src="%7Bmovie.Poster%7D" alt="{movie.Title}">
            <h3 id="movie-Title">{movie.Title}</h3>
          </div>
        ))}
      </div>
    </div>
  );
};

export default Movies;

Code Snippet from Series.js:

import React, { useEffect, useState } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";

const Series = () => {
  const [series, setSeries] = useState([]);
  const navigate = useNavigate();

  useEffect(() => {
    axios
      .get(`http://www.omdbapi.com/?s=Star Wars&type=series&apikey=${process.env.REACT_APP_OMDB_API_KEY}`)
      .then(response => setSeries(response.data.Search || []))
      .catch(error => console.error(error));
  }, []);

  const handleCardClick = (id) => {
    navigate(`/detail/${id}`);
  };

  return (
    <div classname="series">
      <h2 id="TV-Series">TV Series</h2>
      <div classname="cards">
        {series.map(show => (
          <div key="{show.imdbID}" classname="card" onclick="{()"> handleCardClick(show.imdbID)}>
            <img src="%7Bshow.Poster%7D" alt="{show.Title}">
            <h3 id="show-Title">{show.Title}</h3>
          </div>
        ))}
      </div>
    </div>
  );
};

export default Series;

5. Navbar Component

The Navbar component allows users to navigate between different pages and perform a search.

Updated Navbar.js

import React, { useState } from "react";
import { NavLink, Link } from "react-router-dom";

const Navbar = () => {
  const [searchQuery, setSearchQuery] = useState("");

  const handleSearch = (event) => {
    if (event.key === 'Enter' && searchQuery.trim()) {
      document.getElementById('search-link').click();
    }
  };

  return (
    <div classname="navbar">
      <div classname="logo">
        <h1 id="Movie-Finder">Movie Finder</h1>
      </div>

      <div classname="page-list">
        <navlink to="/">
          <h4 id="Home">Home</h4>
        </navlink>
        <navlink to="/movies">
          <h4 id="Movies">Movies</h4>
        </navlink>
        <navlink to="/series">
          <h4 id="TV-Series">TV Series</h4>
        </navlink>
      </div>
      <div classname="search-box">
        <input type="text" placeholder="Search for movies or series" value="{searchQuery}" onchange="{(e)"> setSearchQuery(e.target.value)}
          onKeyDown={handleSearch}
        />
        <link to="{`/search/${searchQuery}`}" id="search-link">
          <button>Search</button>
        
      </div>
    </div>
  );
};

export default Navbar;

6. Footer Component

The Footer component provides a simple footer message.

Footer.js

import React from 'react';

const Footer = () => {
  return (
    <div classname="footer">
      Made with <span>❤️</span> by Abhishek Gurjar
    </div>
  );
};

export default Footer;
*{
  box-sizing: border-box;
}
body{
  font-family: sans-serif;
  margin: 0;
  padding: 0;
}
.navbar {
  padding-inline: 100px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background-color: red;
}
.search-btn{
  background-color: red;
}
.logo h1{
  font-size: 25px;
  color:black;
  }
.page-list {
  display: flex;
  align-items: center;
  gap: 40px;
}

.page-list a{
  color: white;
  text-decoration: none;
  font-size: 20px;
}
.page-list a:hover{
color: black;
}
.page-list a.active{
  color: black;
}
.search-box{
  box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
  background-color:white;
  color: gray;
  width: 250px;
  height: 40px;
  border-radius: 50px;
  overflow: hidden;
}
.search-box input{
  width: 200px;
  height: 40px;
  margin-left: 10px;
  border: none;
  outline: none;

}
.home{
  margin-block: 40px;
  margin-inline: 60px;

}
.home h4{
  font-size: 16px;
}


.movies{
  margin-block: 40px;
  margin-inline: 60px;

}
.movies h4{
  font-size: 16px;
}
.cards{
  display: flex;
flex-wrap: wrap;
  align-items:center ;
  justify-content: space-between;
  gap: 10px;
}
.card{
  width:200px;
  height:360px;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
}
.card img{
  width: 200px;
  height: 290px;
  object-fit: cover;
}
.card h2{
  margin: 10px;
  font-size: 16px;
text-align: center;
}

.series{
  margin-block: 40px;
  margin-inline: 60px;
}
.series h4{
  font-size: 16px;
}
.home{
  margin-block: 40px;
  margin-inline: 60px;

}
.search-results{
  margin-block: 40px;
  margin-inline: 60px;
}
.search-results h4{
  font-size: 16px;
}

.loader{
  min-height: 90vh;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* HTML: <div class="loader"></div> */
.load {
  width: 50px;
  padding: 8px;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #ff1900;
  --_m: 
    conic-gradient(#0000 10%,#000),
    linear-gradient(#000 0 0) content-box;
  -webkit-mask: var(--_m);
          mask: var(--_m);
  -webkit-mask-composite: source-out;
          mask-composite: subtract;
  animation: l3 1s infinite linear;
}
@keyframes l3 {to{transform: rotate(1turn)}}


.movie-detail {
  margin-block: 40px;
  margin-inline: 60px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}
img-box{
  width: 50%;
}
.movie-detail img {
  border-radius: 10px;
width: 330px;
 height: auto;
 object-fit: cover;
 box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
}

.detail-box{
  width: 50%;
}
.movie-detail p {
  font-size: 18px;
  margin: 10px 0;
}

.movie-detail a {
  display: inline-block;
  margin-top: 20px;
  color: #007bff;
  text-decoration: none;
}

.movie-detail a:hover {
  text-decoration: underline;
}


.footer{
  width: 100%;
  background-color: red;
  text-align: center;
  color: white;
  padding: 20px;
}

Live Demo

You can check out the live demo of the Movie Finder website here.

Conclusion

In this blog, we learned how to create a Movie Finder website using React, React Router, and Axios. This project demonstrates how to interact with a public API, manage state in React, and create a simple yet functional movie browsing experience.

Feel free to customize the design and add more features, like user reviews or movie ratings, to make it more dynamic!


Credits

  • OMDB API
  • React
  • React Router

Author

Abhishek Gurjar is a dedicated web developer passionate about creating practical and functional web applications. Check out more of his projects on GitHub.

The above is the detailed content of Building a Movie Finder Website using React. For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Two Images and an API: Everything We Need for Recoloring ProductsTwo Images and an API: Everything We Need for Recoloring ProductsApr 15, 2025 am 11:27 AM

I recently found a solution to dynamically update the color of any product image. So with just one of a product, we can colorize it in different ways to show

Weekly Platform News: Impact of Third-Party Code, Passive Mixed Content, Countries with the Slowest ConnectionsWeekly Platform News: Impact of Third-Party Code, Passive Mixed Content, Countries with the Slowest ConnectionsApr 15, 2025 am 11:19 AM

In this week's roundup, Lighthouse sheds light on third-party scripts, insecure resources will get blocked on secure sites, and many country connection speeds

Options for Hosting Your Own Non-JavaScript-Based AnalyticsOptions for Hosting Your Own Non-JavaScript-Based AnalyticsApr 15, 2025 am 11:09 AM

There are loads of analytics platforms to help you track visitor and usage data on your sites. Perhaps most notably Google Analytics, which is widely used

It's All In the Head: Managing the Document Head of a React Powered Site With React HelmetIt's All In the Head: Managing the Document Head of a React Powered Site With React HelmetApr 15, 2025 am 11:01 AM

The document head might not be the most glamorous part of a website, but what goes into it is arguably just as important to the success of your website as its

What is super() in JavaScript?What is super() in JavaScript?Apr 15, 2025 am 10:59 AM

What's happening when you see some JavaScript that calls super()?.In a child class, you use super() to call its parent’s constructor and super. to access its

Comparing the Different Types of Native JavaScript PopupsComparing the Different Types of Native JavaScript PopupsApr 15, 2025 am 10:48 AM

JavaScript has a variety of built-in popup APIs that display special UI for user interaction. Famously:

Why Are Accessible Websites so Hard to Build?Why Are Accessible Websites so Hard to Build?Apr 15, 2025 am 10:45 AM

I was chatting with some front-end folks the other day about why so many companies struggle at making accessible websites. Why are accessible websites so hard

The `hidden` Attribute is Visibly WeakThe `hidden` Attribute is Visibly WeakApr 15, 2025 am 10:43 AM

There is an HTML attribute that does exactly what you think it should do:

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: How To Unlock Everything In MyRise
1 months agoBy尊渡假赌尊渡假赌尊渡假赌

Hot Tools

SublimeText3 Linux new version

SublimeText3 Linux new version

SublimeText3 Linux latest version

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Integrate Eclipse with SAP NetWeaver application server.

VSCode Windows 64-bit Download

VSCode Windows 64-bit Download

A free and powerful IDE editor launched by Microsoft

Dreamweaver Mac version

Dreamweaver Mac version

Visual web development tools

Atom editor mac version download

Atom editor mac version download

The most popular open source editor