DEV Community

Robert
Robert

Posted on • Updated on

Create a simple React movie database app with The OMDB api

In the next lines of this post, we will be creating a simple react app with OMDB api (https://www.omdbapi.com/), the steps to get a key are very easy, just go to the website, click on API key, select free and type your email, check your inbox and thats it.

You can contact me by telegram if you need to hire a Full Stack developer.

Creating our Project

  1. open your terminal and type following
  2. npx create-react-app react-omdb
  3. cd react-omdb
  4. code .

The CSS used for this example is very simple, you can copy or download it from this link (https://github.com/rtagliaviaz/react-omdb-tut/blob/main/src/App.css) or make your own.

Project file structure:

react-omdb/
├── node_modules/
├── public/
├── src/
│ ├── components/
│ ├── App.css
│ ├── App.js
│ └── index.css
│ └── index.js
└── package.json

Packages

  • axios

For this example we will only install axios as an additional dependency, to install it just open your terminal located in our project followed by npm i axios.

Finally to be able to start, we will reopen the console and we will execute the following command npm start to be able to see the changes that we will make throughout this post.

Content table.

  1. Creation of the Main.js component
  2. API integration
  3. Obtaining movie information
  4. Creation and configuration of the modal to show the movie details
  5. Pagination
  6. Conclusion

Let's get started!

  1. Creation of the Main.js component.

The first thing we will do is delete the files that we won't use, leaving our project as shown above in the project structure.

Then We will open our App.js file located inside src, we will remove the import of the logo, and we will modify our file leaving it as follows for the moment.

import './App.css';

function App() {
  return (
    <div className="App">
      REACT OMDB
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

After this we will go to our components folder inside the src folder (we will create it in case we haven't created it yet), inside components we will create a file called Main.js .

We will open our Main.js, we will begin importing the hooks useState and useEffect since we will use them later, and we will also import axios.

We will create a constant with the name of api where we will place our API address, in this case 'https://www.omdbapi.com/?' and also a constant for apiKey to which we will assign our key.

At the moment we will only return a div with the name of our component.

import React, {useState, useEffect} from 'react'
import axios from 'axios'

//api

const api = 'https://www.omdbapi.com/?'

//api key
const apiKey = 'apikey=18eaeb4f'

const Main = () => {

  return(
    <div>
      Main
    </div>
  )
}

export default Main
Enter fullscreen mode Exit fullscreen mode

We will return to our App.js and we will import our Main.js component as shown in the code below, we will see that it already shows us the Main.js component.

import React from 'react';
import './App.css'
//components
import Main from './components/Main'

function App() {
  return (
    <div className="App">
      REACT OMDB
      <Main />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Hello World

  1. API integration.

Now we will return to our Main.js component and we will make a form to be able to make a search with the movie name, and to make use of the useState hook. When clicking to the search button the function will be executed and will make a GET request to the api, and at the moment we will show the response by console.

import React, { useState, useEffect } from "react";
import axios from "axios";

//api

const api = "https://www.omdbapi.com/?";

//api key
const apiKey = "apikey=18eaeb4f";

const Main = () => {
  const [name, setName] = useState("");

  //get response from API
  const getInfo = () => {
    axios
      .get(api + apiKey + `&s=${name}` + "&type=movie" + "&page=1")
      .then((res) => {
        if (res) {
          console.log(res.data);
        }
      });
  };

  //submit the title entered
  const handleSubmit = (e) => {
    e.preventDefault();
    getInfo();
  };

  return (
    <div>
      <form>
        <div className='searchBar'>
          <label htmlFor='name'></label>
          <input
            type='text'
            name='name'
            placeholder='movie name'
            onChange={(e) => setName(e.target.value)}
          />
          <button type='submit' onClick={(e) => handleSubmit(e)}>
            Search
          </button>
        </div>
      </form>
    </div>
  );
};

export default Main;
Enter fullscreen mode Exit fullscreen mode

api response

We are receiving an object, with the total results (which we will use later to create the pagination), and an array with the first 10 movies it found, with a couple of details such as the poster, the release date, the imdbID (which we will also use later), and many more.

We will use useState again, in this case with an array of movies with an empty initial state, and it will change when we get the results. And now in the return() we will be rendering the list of results if the array has elements, otherwise we will return null.

for each each title we will render a poster, the title, and a details button(that at the moment does nothing), and we will use the imdbId as the key.

import React, { useState, useEffect } from "react";
import axios from "axios";

//api

const api = "https://www.omdbapi.com/?";

//api key
const apiKey = "apikey=18eaeb4f";

const Main = () => {
  const [name, setName] = useState("");
  const [movies, setMovies] = useState([])

  //get response from API
  const getInfo = () => {
    axios
      .get(api + apiKey + `&s=${name}` + "&type=movie" + "&page=1")
      .then((res) => {
        if (res) {
          setMovies(res.data.Search)
        }
      });
  };

  //submit the title entered
  const handleSubmit = (e) => {
    e.preventDefault();
    getInfo();
  };

  return (
    <div>
      <form>
        <div className='searchBar'>
          <label htmlFor='name'></label>
          <input
            type='text'
            name='name'
            placeholder='movie name'
            onChange={(e) => setName(e.target.value)}
          />
          <button type='submit' onClick={(e) => handleSubmit(e)}>
            Search
          </button>
        </div>
      </form>

      {movies ? 
      <div className="movies">
        {movies.map(movie => (
          <div key={movie.imdbID} className="movie">
            <img src={movie.Poster} alt=""/>
            <div className="movie-title">
              <p>{movie.Title}</p>
            </div>
            <button className="movie-detailsBtn" >Details</button>

          </div>))}
      </div> 
      : null}
    </div>
  );
};

export default Main;
Enter fullscreen mode Exit fullscreen mode

movies displayed

  1. Obtaining movie information.

Now for the details button we will create a function named getDetails, which will be have and id as argument (imdbID of the title),

We are gonna make another GET request to the api with the id, an it will return the movie information, at the moment we will show it with a console.log()

import React, { useState, useEffect } from "react";
import axios from "axios";

//api
const api = "https://www.omdbapi.com/?";

//api key
const apiKey = "apikey=18eaeb4f";

const Main = () => {
  const [name, setName] = useState("");
  const [movies, setMovies] = useState([])

  //get response from API
  const getInfo = () => {
    axios
      .get(api + apiKey + `&s=${name}` + "&type=movie" + "&page=1")
      .then((res) => {
        if (res) {
          setMovies(res.data.Search)
        }
      });
  };



  //submit the title entered
  const handleSubmit = (e) => {
    e.preventDefault();
    getInfo();
  };

  return (
    <div>
      <form>
        <div className='searchBar'>
          <label htmlFor='name'></label>
          <input
            type='text'
            name='name'
            placeholder='movie name'
            onChange={(e) => setName(e.target.value)}
          />
          <button type='submit' onClick={(e) => handleSubmit(e)}>
            Search
          </button>
        </div>
      </form>

      {movies ? 
      <div className="movies">
        {movies.map(movie => (
          <div key={movie.imdbID} className="movie">
            <img src={movie.Poster} alt=""/>
            <div className="movie-title">
              <p>{movie.Title}</p>
            </div>
            <button className="movie-detailsBtn" >Details</button>

          </div>))}
      </div> 
      : null}
    </div>
  );
};

export default Main;
Enter fullscreen mode Exit fullscreen mode

movie details from api

Let's add a new state named movieDetails that will be initialized with an empty object, and will be updated with the movie details.

import React, { useState, useEffect } from "react";
import axios from "axios";

//api

const api = "https://www.omdbapi.com/?";

//api key
const apiKey = "apikey=18eaeb4f";

const Main = () => {
  const [name, setName] = useState("");
  const [movies, setMovies] = useState([])
  const [movieDetails, setMovieDetails] = useState({})

  //get response from API
  const getInfo = () => {
    axios
      .get(api + apiKey + `&s=${name}` + "&type=movie" + "&page=1")
      .then((res) => {
        if (res) {
          setMovies(res.data.Search)
        }
      });
  };

  //get details
  const getDetails = (e, id) => {
    e.preventDefault()

    axios.get(api + apiKey + `&i=${id}`).then((res) => {
      if (res) {
        setMovieDetails(res.data)
      }
    })
  }

  //submit the title entered
  const handleSubmit = (e) => {
    e.preventDefault();
    getInfo();
  };

  return (
    <div>
      <form>
        <div className='searchBar'>
          <label htmlFor='name'></label>
          <input
            type='text'
            name='name'
            placeholder='movie name'
            onChange={(e) => setName(e.target.value)}
          />
          <button type='submit' onClick={(e) => handleSubmit(e)}>
            Search
          </button>
        </div>
      </form>

      {movies ? 
      <div className="movies">
        {movies.map(movie => (
          <div key={movie.imdbID} className="movie">
            <img src={movie.Poster} alt=""/>
            <div className="movie-title">
              <p>{movie.Title}</p>
            </div>
            <button className="movie-detailsBtn" 
            onClick={e => getDetails(e, movie.imdbID)}
            >Details</button>

          </div>))}
      </div> 
      : null}
    </div>
  );
};

export default Main;
Enter fullscreen mode Exit fullscreen mode
  1. Creation and configuration of the modal to show the movie details

To show the data, we will be using a modal that will be displayed every time we click the "details" button, in order to make this, we are gonna make a new file named MovieModal.js inside the components folder, by the moment will render this structure.

const MovieModal = () => {
  return(
    <div className="modal display-block">
      <section className="modal-main">
        <div className="modal-body">

        </div>
        <button className="modal-closebtn" >Close</button>
      </section>
    </div>
  )
}

export default MovieModal
Enter fullscreen mode Exit fullscreen mode

In order to open the modal lets go back to Main.js:

1- Let's begin importing our MovieModal.js component.

2- Let's make a new state $const [selectedId, setSelectedId] = useState(null).

3- Let's create another state to show the modal $const [show, setShow] = useState(false) it will be a boolean initialized with a false value.

4- We will manage our modal functionality with three functions.

5- In our getDetails function we will update the selectId state with the id passes as argument, then after getting the response from the API we will open the modal with showModal().

6- Finally in the return() of our component we will conditionally render the modal if MovieDetails isn't empty, if the selectedId is strictly equals to the imdbID of the movie and if show is true, otherwise it won't show it.

import React, { useState, useEffect } from "react";
import axios from "axios";

//components
import MovieModal from "./MovieModal";

//api
const api = "https://www.omdbapi.com/?";

//api key
const apiKey = "apikey=18eaeb4f";

const Main = () => {
  const [name, setName] = useState("");
  const [movies, setMovies] = useState([])
  const [selectedId, setSelectedId] = useState(null)
  const [movieDetails, setMovieDetails] = useState({})

  //modal
  const [show, setShow] = useState(false)


  //modal config

  const showModal = () => {
    setShow(true)
  }

  const hideModal = () => {

    setShow(false)
    setMovieDetails()
  }

  const handleClose = () => {
    hideModal()
  }

  //get response from API
  const getInfo = () => {
    axios
      .get(api + apiKey + `&s=${name}` + "&type=movie" + "&page=1")
      .then((res) => {
        if (res) {
          setMovies(res.data.Search)
        }
      });
  };

  //get details
  const getDetails = (e, id) => {
    e.preventDefault()

    setSelectedId(id)
    axios.get(api + apiKey + `&i=${id}`).then((res) => {
      if (res) {
        setMovieDetails(res.data)
        showModal()
      }
    })
  }

  //submit the title entered
  const handleSubmit = (e) => {
    e.preventDefault();
    getInfo();
  };

  return (
    <div>
      <form>
        <div className='searchBar'>
          <label htmlFor='name'></label>
          <input
            type='text'
            name='name'
            placeholder='movie name'
            onChange={(e) => setName(e.target.value)}
          />
          <button type='submit' onClick={(e) => handleSubmit(e)}>
            Search
          </button>
        </div>
      </form>

      {movies ? 
      <div className="movies">
        {movies.map(movie => (
          <div key={movie.imdbID} className="movie">
            <img src={movie.Poster} alt=""/>
            <div className="movie-title">
              <p>{movie.Title}</p>
            </div>
            <button className="movie-detailsBtn" 
            onClick={e => getDetails(e, movie.imdbID)}
            >Details</button>

            {/* modal */}
            {movieDetails && (selectedId===movie.imdbID) && show ? 
              <MovieModal/> : 
              <div className="modal display-none"></div>
            }

          </div>))}
      </div> 
      : null}
    </div>
  );
};

export default Main;
Enter fullscreen mode Exit fullscreen mode

Modal is working

As you may have noticed, we are not displaying any information and we can't close it either. Let's go back to Main.js, go to the return() part and in the modal rendering, let's pass handleclose as a prop and also the details named as movieInfo.

{/* modal */}
{movieDetails && (selectedId===movie.imdbID) && show ? 
  <MovieModal 
  movieInfo={movieDetails} 
  handleClose={handleClose}/> : 
  <div className="modal display-none"></div>
}
Enter fullscreen mode Exit fullscreen mode

Let's go back to MovieModal.js, we will pass the props like this.

const MovieModal = ({movieInfo, handleClose}) => {
  return(
    .
    .
    .
  )
}
export default MovieModal
Enter fullscreen mode Exit fullscreen mode

Now lets modify the return() to return some data, and lets add the functionality to our button with the handleClose function when we click it.

const MovieModal = ({ movieInfo, handleClose }) => {
  return (
    <div className='modal display-block'>
      <section className='modal-main'>
        <div className='modal-body'>
          <div className='modal-img'>
            <img src={movieInfo.Poster} alt='Poster' />
          </div>
        </div>
        <div className='modal-info'>
          <p>
            <b>Actors:</b> {movieInfo.Actors}
          </p>
          <p>
            <b>Genre:</b> {movieInfo.Genre}
          </p>
          <p>
            <b>Director:</b> {movieInfo.Director}
          </p>
          <p>
            <b>Released:</b> {movieInfo.Released}
          </p>
          <p>
            <b>Plot:</b> {movieInfo.Plot}
          </p>
        </div>
        <button className='modal-closebtn' onClick={handleClose}>
          Close
        </button>
      </section>
    </div>
  );
};

export default MovieModal;
Enter fullscreen mode Exit fullscreen mode

modal with details

  1. Pagination.

Let's create the pagination to view the rest of the results.

We need to go back to Main.js, if we remember when we make the GET request we obtain an object with a property named totalResults, this property is the key to make the pagination.

total results

First we will add a new state, then we will modify our getInfo function to make an update to the totalReuslts state.

const Main = () => {
  const [name, setName] = useState("");
  const [movies, setMovies] = useState([])
  const [selectedId, setSelectedId] = useState(null)
  const [movieDetails, setMovieDetails] = useState({})

  //modal
  const [show, setShow] = useState(false)

  //pagination
  const [totalResults, setTotalResults] = useState()
  .
  .
  .

  //get response from API
  const getInfo = () => {
    axios
      .get(api + apiKey + `&s=${name}` + "&type=movie" + "&page=1")
      .then((res) => {
        if (res) {
          setMovies(res.data.Search)
          setTotalResults(res.data.totalResults)
        }
      });
  };

  .
  .
  .
Enter fullscreen mode Exit fullscreen mode

Thanks to totalResults now we can calculate the number of pages that will have the search that we perform, we will create a new state const [numberOfPages, setNumberOfPages] = useState() then we will build a function named getNumberOfPages to get the number of pages from our search.

In the function we will divide by 10 because we can only get 10 results per page, so we will use the remainder operator %, to verify if we are gettin left overs, in that case we will add an extra page. We will make use of parseInt() because totalResults is a string. Finally we will update numberOfPages with the number of pages.

.
.
.
//pagination
  const [totalResults, setTotalResults] = useState()
  const [numberOfPages, setNumberOfPages] = useState()
.
.
.

const getNumberOfPages = () => {
  if (totalResults % 10 > 0) {
    const numberOfpages = parseInt((totalResults / 10) + 1)
    setNumberOfPages(numberOfpages)
    return
  }
    const numberOfpages = parseInt(totalResults / 10)
    setNumberOfPages(numberOfpages)
}
Enter fullscreen mode Exit fullscreen mode

Now we will create a state that will be updated with the current page (selected page) const [currentPage, setCurrentPage] = useState() .

We're going to modify the getInfo() function to make it update the currentPage with the value of 1, when we do the first search, and we will start rendering the pagination.

If we already have the number of pages, then we will render a div with the pagination, otherwise we won't render it. if the previous page currentPage -1 is equals to 0 we won't show it, otherwise we will show currentPage - 1, then the current page currentPage, lastly the next page currentPage + 1.

.
.
.
//pagination
const [totalResults, setTotalResults] = useState()
const [numberOfPages, setNumberOfPages] = useState()
const [currentPage, setCurrentPage] = useState()
.
.
.

//get response from API
const getInfo = () => {
  axios
    .get(api + apiKey + `&s=${name}` + "&type=movie" + "&page=1")
    .then((res) => {
      if (res) {
        setMovies(res.data.Search);
        setTotalResults(res.data.totalResults);
        setCurrentPage(1)
      }
    });
};


return(
  {numberOfPages ? (
    <div className='pages'>
      {/* if prev page is 0 it wont show */}
      {currentPage - 1 === 0 ? null : (
        <b >{currentPage - 1}</b>
      )}
      <b  className='actualPage'>
        {currentPage}
      </b>
      <b >{currentPage + 1}</b>
    </div>
  ) : null}
)
Enter fullscreen mode Exit fullscreen mode

pagination

For now we can only see the pagination but is not working, let's make it work.

1- We will use useEffect() and make it call getNumbersOfPages() .

2- Now we are going to modify getInfo() function and we will pass pageNumber as an argument, so if we have the pageNumber we will pass it, otherwise we will use pageNumber with 1 as a default value.

3- We will make an array of pages and we will fill it with a for loop, that will iterate numberOfPages, example ( if numberOfPages = 5, then will push 5 elements in the array ).

4- We will create a function named goTo(pageNumber) and we will pass pageNumber as an argument, after we call this function we will update the currentPage state with the selected page value pageNumber. Finally it will call the getInfo function .

5- We will modify our Pagination rendering, now when we click the page it will execute the goTo function with the selected page

.
.
.

//get response from API
const getInfo = (pageNumber) => {
  if (pageNumber) {
    axios
      .get(
        api + apiKey + `&s=${name}` + "&type=movie" + `&page=${pageNumber}`
      )
      .then((res) => {
        if (res) {
          setMovies(res.data.Search);
          setTotalResults(res.data.totalResults);
        }
      });
    return;
  }
  axios
    .get(api + apiKey + `&s=${name}` + "&type=movie" + "&page=1")
    .then((res) => {
      if (res) {
        setMovies(res.data.Search);
        setTotalResults(res.data.totalResults);
        setCurrentPage(1);
      }
    });
};

//getnumberOFpageseffect
useEffect(() => {
  getNumberOfPages();
});

const pages = [];

for (let i = 1; i <= numberOfPages; i++) {
  pages.push(<p key={i} onClick={e => goTo(i)}>{i}</p>)
}

const goTo = (pageNumber) => {

  setCurrentPage(pageNumber)
  getInfo(pageNumber)
  window.scrollTo(0, 0)
}


return(
  .
  .
  .
  {numberOfPages ? (
    <div className='pages'>
      {/* if prev page is 0 it wont show */}
      {currentPage - 1 === 0 ? null : <b onClick={e => goTo(currentPage-1)}>{currentPage - 1}</b>}
      <b onClick={e => goTo(currentPage)}className='actualPage'>{currentPage}</b>
      <b onClick={e => goTo(currentPage+1)}>{currentPage + 1}</b>
    </div>
  ) : null}
)
Enter fullscreen mode Exit fullscreen mode

This is our file after all the modifications.

import React, { useState, useEffect } from "react";
import axios from "axios";

//components
import MovieModal from "./MovieModal";

//api
const api = "https://www.omdbapi.com/?";

//api key
const apiKey = "apikey=18eaeb4f";

const Main = () => {
  const [name, setName] = useState("");
  const [movies, setMovies] = useState([]);
  const [selectedId, setSelectedId] = useState(null);
  const [movieDetails, setMovieDetails] = useState({});

  //modal
  const [show, setShow] = useState(false);

  //pagination
  const [totalResults, setTotalResults] = useState(0);
  const [numberOfPages, setNumberOfPages] = useState();
  const [currentPage, setCurrentPage] = useState();

  const getNumberOfPages = () => {
    if (totalResults % 10 > 0) {
      const numberOfpages = parseInt(totalResults / 10 + 1);
      setNumberOfPages(numberOfpages);
      return;
    }
    const numberOfpages = parseInt(totalResults / 10);
    setNumberOfPages(numberOfpages);
  };

  //modal config
  const showModal = () => {
    setShow(true);
  };

  const hideModal = () => {
    setShow(false);
    setMovieDetails();
  };

  const handleClose = () => {
    hideModal();
  };

  //get response from API
  const getInfo = (pageNumber) => {
    if (pageNumber) {
      axios
        .get(
          api + apiKey + `&s=${name}` + "&type=movie" + `&page=${pageNumber}`
        )
        .then((res) => {
          if (res) {
            setMovies(res.data.Search);
            setTotalResults(res.data.totalResults);
          }
        });
      return;
    }
    axios
      .get(api + apiKey + `&s=${name}` + "&type=movie" + "&page=1")
      .then((res) => {
        if (res) {
          setMovies(res.data.Search);
          setTotalResults(res.data.totalResults);
          setCurrentPage(1);
        }
      });
  };

  //get details
  const getDetails = (e, id) => {
    e.preventDefault();

    setSelectedId(id);
    axios.get(api + apiKey + `&i=${id}`).then((res) => {
      if (res) {
        setMovieDetails(res.data);
        showModal();
      }
    });
  };

  //submit the title entered
  const handleSubmit = (e) => {
    e.preventDefault();
    getInfo();
  };

  //getnumberOFpageseffect
  useEffect(() => {
    getNumberOfPages();
  });

  const pages = [];

  for (let i = 1; i <= numberOfPages; i++) {
    pages.push(
      <p key={i} onClick={(e) => goTo(i)}>
        {i}
      </p>
    );
  }

  const goTo = (pageNumber) => {
    setCurrentPage(pageNumber);
    getInfo(pageNumber);
    window.scrollTo(0, 0);
  };

  return (
    <div>
      <form>
        <div className='searchBar'>
          <label htmlFor='name'></label>
          <input
            type='text'
            name='name'
            placeholder='movie name'
            onChange={(e) => setName(e.target.value)}
          />
          <button type='submit' onClick={(e) => handleSubmit(e)}>
            Search
          </button>
        </div>
      </form>

      {movies ? (
        <div className='movies'>
          {movies.map((movie) => (
            <div key={movie.imdbID} className='movie'>
              <img src={movie.Poster} alt='' />
              <div className='movie-title'>
                <p>{movie.Title}</p>
              </div>
              <button
                className='movie-detailsBtn'
                onClick={(e) => getDetails(e, movie.imdbID)}
              >
                Details
              </button>

              {/* modal */}
              {movieDetails && selectedId === movie.imdbID && show ? (
                <MovieModal
                  movieInfo={movieDetails}
                  handleClose={handleClose}
                />
              ) : (
                <div className='modal display-none'></div>
              )}
            </div>
          ))}
        </div>
      ) : null}

      {numberOfPages ? (
        <div className='pages'>
          {/* if prev page is 0 it wont show */}
          {currentPage - 1 === 0 ? null : (
            <b onClick={(e) => goTo(currentPage - 1)}>{currentPage - 1}</b>
          )}
          <b onClick={(e) => goTo(currentPage)} className='actualPage'>
            {currentPage}
          </b>
          <b onClick={(e) => goTo(currentPage + 1)}>{currentPage + 1}</b>
        </div>
      ) : null}
    </div>
  );
};

export default Main;
Enter fullscreen mode Exit fullscreen mode
  1. Conclusion.

In this post we learned how to integrate an API to a react application in a simple way.

I really hope you have been able to follow the post without any trouble, otherwise I apologize, please leave me your doubts or comments.

Like I said before, the interface that I created for this example is very simple, it can be improved as well as the code, I encourage you to improve it and add more features.

You can contact me by telegram if you need to hire a Full Stack developer..

You can clone the repo if you want..

Thanks for your time.

Top comments (0)