Stop any project you currently have running; let's go back to the film application that you've started. You can run the app with npm start.
You're almost finished! Now, You need to:
- Show the details of each movie by getting this information from TMDB.
- Refactor your React app to make it as clean as possible.
We'll use the 3rd party Axios API to handle our API calls.
You already have a function where you want the API call to go (handleDetailsClick - when a user clicks for details of a movie, you'll call the API to get those details), so your axios task will work inside that function. You've set up the rest of your app correctly.
This step seems complicated, but it isn't! Just take it one step at a time. Because TMDB isn't a public API, you'll need to get an API key to add to your fetch() call; then, you'll want to make sure to keep the key in a safe spot.
-
To gain access to the TMDB API, you'll need to get an API key from TMDB.
- TMDB only gives API keys to users with accounts, sou'll have to sign up first (it's free). However, it will ask for your phone and address.
- Then, request an API key on your profile page (further instructions).
- Once you have your API key, you need to include it in your app. Since you never want to store app secrets in your repository, you'll use the
dotenvpackage to keep the API key in a local file.
-
You'll need to install
dotenv.- Run
npm install --save dotenvon the command line to add the dependency to yourpackage.jsonfile - Create a new file at the root of your project called
.env.local(accept the system warning). - In your
.env.localfile, add the lineREACT_APP_TMDB_API_KEY=<Your TMDB API v3 KEY> - Make sure that you restart youor React server.
- Run
Note: The .env.local file is in your .gitignore by default when you create an app with create-react-app, so now your secret will never leak into your repository. It's important to note that since this is a front-end application, the built JavaScript will contain the key, which means end-users will be able to see it. However, that's fine for this practice app, since you'll only be running it locally.
- Now you have an API key saved in
dotenv. Now, point your application to it: add the following to the top of yourTMDB.jsfile:
import dotenv from 'dotenv';
dotenv.config();- And replace
'<REPLACE_THIS_WITH_TMDB_API_KEY>'withprocess.env.REACT_APP_TMDB_API_KEY.
Your secrets are now set up!
Now that you have the API key to call for movie details, let's go back to making that call.
In your App.js handleDetailsClick method, add the following const right above your setState:
const url = `https://api.themoviedb.org/3/movie/${film.id}?api_key=${TMDB.api_key}&append_to_response=videos,images&language=en`This is the URL to which you'll send your request to get detailed information about each film. You're passing the film.id and the TMDB.api_key as query string parameters.
- Note: Using
${film.id}is a slightly faster shorthand for embedding variables in strings.- For example,
const myString = "The " + film.id + " is great"is the same as writingconst myString = "The ${film.id} is great".
- For example,
Run npm install axios and add import axios from "axios" to the top of App.js.
Now that you have the API key and URL set up, underneath the new URL variable, fetch the API.
const url = `https://api.themoviedb.org/3/movie/${film.id}?api_key=${TMDB.api_key}&append_to_response=videos,images&language=en`
axios({
method: 'GET',
url: url
}).then(response => {
console.log(response) // take a look at what you get back!
})Try clicking a movie row in your browser - the data for it should appear in the console.
Let's now set your current state to be the object you get back from TMDB. Move the setState call into the API call.
axios({
method: 'GET',
url: url
}).then(response => {
console.log(response) // take a look at what you get back!
console.log(`Fetching details for ${film.title}`);
this.setState({ current: response.data })
})Now, you have the API call to get information about your chosen movie.
Before you continue to display the movie details to the user, let's clean up your application.
Let's refactor any components that only have a render() method into functional components. Functional components are simpler and will gain performance benefits in future versions of React. It is considered good practice to use them wherever possible.
- Replace the
class/extendsdefinition with afunction. Remember that your function should accept apropsargument. - Remove the
render()method, keeping only thereturnfunction. - Replace all instances of
this.propswith simplyprops - Remove
{Component}from the Reactimportat the top since you no longer use it (but still importReact).
Check in your browser to be sure the functionality hasn't changed.
You haven't written out the FilmDetails component yet, but it currently only renders UI. Therefore, you can also make it a functional component.
Follow the same steps as above, and once again check in the browser for functionality.
The FilmRow component currently only renders UI. Therefore, you can also make it a functional component.
Follow the same steps as above, and once again check in the browser for functionality.
You're almost finished. Now, you'll render the film details you're receiving from the API (and currently logging to the console) in the browser window for the user.
Above the return, add the following const definitions for fetching backdrop and posters:
const backdropUrl = `https://image.tmdb.org/t/p/w1280/${props.film.backdrop_path}`
const posterUrl = `https://image.tmdb.org/t/p/w780/${props.film.poster_path}`When the app loads, there is no film selected to display in FilmDetails. When a user clicks on a film in the FilmListing, you want to fetch and show the details. Thus, there are two scenarios for FilmDetails:
- The empty scenario (no film selected)
- The populated scenario (a film selected)
Start with the empty case. Add the following markup below the .section-title.
<div className="film-detail">
<p>
<i className="material-icons">subscriptions</i>
<span>No film selected</span>
</p>
</div>To start, create a new variable to hold on to your DOM tree. You'll conditionally assign the value to this variable depending on whether or not there's a film object passed in through the props.
Add this below the two declared const variables:
let detailsNow, you need to determine if there is a film to render or not.
To do this, you just need to check if there's an id property on the film prop passed in to FilmDetail.
- If not, you want to render the empty case you added in the last step.
- Otherwise, you have a film to show, so you want to present the film details markup (don't copy this over yet):
<div className="film-detail is-hydrated">
<figure className="film-backdrop">
<img src={backdropUrl} alt="" />
<h1 className="film-title">{props.film.title}</h1>
</figure>
<div className="film-meta">
<h2 className="film-tagline">{props.film.tagline}</h2>
<p className="film-detail-overview">
<img src={posterUrl} className="film-detail-poster" alt={props.film.title} />
{props.film.overview}
</p>
</div>
</div>- Your task here is to conditionally assign the film details block of markup to the
detailsvariable if there is a currentid.- If there is not a current
id, instead render the JSX for the empty case.
- If there is not a current
- You still want to keep your
section-title, which isn't part of this conditional.- Therefore, the
returnstatement of yourFilmDetailsfunction should finally look like this:
- Therefore, the
return (
<div className="film-details">
<h1 className="section-title">Details</h1>
{detail}
</div>
)Here are some optional things you can do to deepen your knowledge and take this app further:
- Refactor
Faveinto a functional component. - Move the filters into a
FilmListingFiltercomponent. - Implement client-side routing to show multiple pages of films.
- Go through the CSS and see how the app is styled (it uses both flexbox and CSS Grid).
- Add a textarea for a review to each film detail and save that on the film object.
- Show an icon in
FilmListingfor all films with reviews. - Show the
favestate of a film onFilmDetails. - Add any other features you can think of!

