Help with MovieDB API in React

I am trying to build my first React App on my own and I am using the MovieDB API. What I am wanting to do is search for a movie title and return the data back from the API. Right now I just want to console.log some data after hitting the search button. Here is my code so far. Is this the right way to do it?

 import React, {useEffect, useState} from 'react';
import './App.css';
import Resturants from './Resturants';

function App() {
const API_KEY = '664e565dee7eaa6ef924c41133a22b63';

const getMovies = async () => {
  const response = await fetch(
    `https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=Avengers&page=1&include_adult=false`
  )
  const data = await response.json()
  console.log(data.results)

}
  return (
   <div>
     <form>
       <input type="text"></input>
       <button onClick={getMovies}type="submit">Search</button>
     </form>
   </div>
  );
}

The fetch call is a side effect. It will console log, but that won’t be much help in practice: that’s going to happen when the fetch promise resolves regardless but the way you have it set up won’t lend itself to rendering stuff onscreen when you want it.

Same process as your previous code, you need some state, then you need to keep that state in sync by using useEffect, which should rerun every time some specific state value/s are changed.

function App() {
  const [movies, setMovies] = React.useState([]);
  const [query, setQuery] = React.useState("");

  React.useEffect(() => {
    const getMovies = async () => {
      const response = await fetch(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`);
      const data = await response.json();
      console.log(data.results)
      setMovies(data.results);
      setQuery("");
    }
    
    if (query) {
      getMovies();
    }
  }, [query]);
      
  return (
   <div>
     <form>
       <input type="text"></input>
       <button onClick={() => setQuery("Avengers")}type="submit">Search</button>
     </form>
   </div>
  );
}

So the useEffect is needed to fetch data and change things in the DOM. And that is all put into a state?

umm, so I’ve written this quite quickly, apologies if there are any minor errors and it might be slightly hard to grok at first so ask for clarification but:

Yes

No.

So you have a function, like

const Component = () => {
  const [count, setCount] = React.useState(0);

  return (
    <button onClick={() => setCount(count => count + 1)}>{ count }</button>
  );
};

count is literally just a variable. So text in the button just renders the values of that variable.

When you click the button, that runs the setCount function. This causes a state change, so React runs the Compent function again, but this time count is 1. And if you click again, setCount gets ran, state changes, the Component function runs again, with count now 2.

For the component function to be called again, either the state needs to change or it needs to get new props. With your example, that isn’t going to happen:

const FetchComponent = () => {
  async function getMovies() { /* async stuff */ }

  return (
    <button onClick={getMovies}>Get Movies</button>
  );
};

There is no state change. You are console logging, and that will log eventually, but as I said, that isn’t any practical use. So say you use state:

const FetchComponent = () => {
  const [movies, SetMovies] = React.useState([])
  async function getMovies() {
    /* async stuff */
   setMovies(response);
  }

  return (
    <button onClick={getMovies}>Get Movies</button>
  );
};

The issue here is that getMovies is asynchronous. setState will happen straightaway, and at that point the response hasn’t come back from the API. So the FetchComponent function will execute again, but this time the movies variable will be undefined. Not what you want.

So this is where useEffect comes in. It allows for side effects. Every useEffect in the function runs every time the function runs. It basically lets you keep the useState value in sync, keeps it correct according to what you want.

So ideally:

  • You have some state that holds the list of movies
  • You click a button, which makes a request
  • wait for the response to return from the api, and only then update the state
  • state change causes the component function to be executed again, this time with a list of moves as the state, which in turn executes the JSX functions that eventually render a list of movies to the DOM.

useEffect is a function that takes two arguments: a function that it runs every time the overall component function is run, and an array of dependencies (I’ll cover that in a minute, it’s important).

The function itself shouldn’t be async, but you can put async function inside it (and execute it, you want the return value from it), which is what I’ll do here:

const FetchComponent = () => {
  const [movies, SetMovies] = React.useState([]);

  React.useEffect(() => {
    async function getMovies() {
      /* async stuff */
     setMovies(response);
    }
   
    getMovies();
  }, []);

  return (
    <button>Get Movies</button>
    <p>{JSON.stringify(movies)}</p>
  );
};

Note that I have no way now to trigger the getMovies function. It will just run when the component first loads, populate the state with whatever comes back. useEffect just keeps things in sync and runs functions you want to run: you can’t explicitly call anything inside it. I can access and modify any state values though.

So to manually make if run the getMovies function, what I need to do is to force the component to rerender and execute the getMovies function. So lets add another state value, one that I will set when the button gets clicked. Now:

  • You have some state that holds the list of movies (an array)
  • You have some state that holds the search term you want to search for (a string)
  • The search term starts as an empty string
  • In useEffect, only run getMovies if the search string is not an empty string
  • You click a button, which sets the search term
  • The component function is called again, and useEffect runs
  • Inside useEffect, the search string is now a nonempty string: getMovies runs.
  • getMovies updates the movies state. Component function is executed again.
  • movies is now a populated list of movies, go off and render to the DOM
const FetchComponent = () => {
  const [movies, SetMovies] = React.useState([]);
  const [searchString, setSearchString] = React.useState("");

  React.useEffect(() => {
    async function getMovies() {
      /* async stuff */
     setMovies(response);
    }
   
    if (searchString !== "") getMovies();
  }, [searchString]);

  return (
    <button onClick={() => setSearchString("Avengers")}>Get Movies</button>
    <p>{JSON.stringify(movies)}</p>
  );
};

Finally, the array of dependencies, the second argument to useEffect. I did say that useEffect runs every time the component function runs. Well, you can modify that behaviour by specifying values in the dependency array. If those values don’t change, then useEffect won’t run again. In this case it’s [serachString] – useEffect should only run once when the component first loads, then only again if the search string changes.

The button sets the search string, the search string value changes, the search string is the dependency here, and so useEffect should run again.

2 Likes

I really apperciate you taking the time to write an in-depth explanation of this. I am starting to understand this a little better.

I do have some things that I have questions on

Why does setMovies happen straight away? Is it because it is a state variable, and the entire state re-renders and that happens before the async function?

Why should it not be async? Again still trying to understand async better and when I should use it

And so is the setSearch just defaulted to Avengers? Is that the first thing that will show up? What happens when I type a title for another movie? Is the searchString variable going to run getMovies and return data for other movies I search for?

Yep, this is just a normal JS thing: setMovies is going to execute before the async resolves – same as for example:

let myData;
fetch("https://example.api.com").then(data => myData = data);
console.log(myData);

The log will be undefined. Unless you move the console log into the then callback, the fetch will resolve at some point after the console logging occurs. Same thing happens with that setMovies – it’ll run the function straightaway and set the movies variable to undefined

So the difference is that you aren’t supposed to do this:

React.useEffect(async () => {
  await /* something */;
  // Do stuff
}, []);

The function you pass as the first argument just gets executed straightaway every time [depending what’s in the dependencies array]. Donuts [Edit: thank you autocomplete] It’s just a normal, synchronous function. Inside that function you can define and execute things that have side effects, and you can read and set the state.

No, nothing will show up at first: the search string state is an empty string, and the fetch doesn’t execute until it’s not an empty string.

Once that state is changed to “Avengers”, the component function will run again, and because the state is not an empty string, the fetch will run. Then the state of the movies changes, but because the search string hasn’t changed, useEffect will not run this time.

So I assume you’re going to want to allow a user to enter something other than “Avengers”. So you can have another state value for the value of the text input. User types, update that state. When they click the button, setSearchString should use that state value. Component function runs again, this time the thing useEffect depends on has changed, the useEffect function runs, fetch runs.

It’s a little hard to get your head around at first, but it works really well once it clicks. IMO thing that’s hardest to grok is that you can’t directly ask useEffect to do things; it’s there to keep the state in sync.

It’s also really easy to accidentally create infinite loops of fetching if you, for example, forget the dependency array, or specify the wrong thing in it. You notice it happening pretty quick though, because it’ll generally lock everything up

This is very good: A Complete Guide to useEffect — overreacted

This is a tutorial on building your own React to learn how it works, including hooks – I haven’t gone through it but people seem to like it a lot: Build your own React

If you’re using Create React App I can’t remember if this is included, but if it isn’t, install this ESLint plugin because it’ll give you detailed warnings about hooks usage: eslint-plugin-react-hooks - npm

So far I’ve added the code based off what you suggested which is

const App = () => {
  const API_KEY = '664e565dee7eaa6ef924c41133a22b63';

  const [movies, setMovies] = useState([]);
  const [query, setQuery] = useState("");

  useEffect(() => {
    async function getMovies(){
      const response = await fetch(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}`)
      const data = await response.json()
      console.log(data.results)
      setMovies(data.results)
    }
    if(query !== "") getMovies();

  }, [query])


  return (
   <div>
     <form>
    
       <button onClick={() => setQuery("Avengers")}type="submit">Search</button>
       <p>{JSON.stringify(movies)}</p>
     </form>
   </div>
  );
}

But I am getting an error saying TypeError: NetworkError when attempting to fetch resource.

Edit: I figured it out. I just had to take off the <form> tags