skip to Main Content

I’m creating a random movie generator, for that I’m calling an external API to get the movies, and then I’m selecting one movie from the returned list. I managed to make it work like this:

export default function Page() {
  const [movie, setMovie] = useState<Movie>({
    title: ""
  })

  useEffect(() => {
    setNewMovie()
  }, [])

  function setNewMovie() {
    getMoviesRequest().then((newMovies) => {
      const randomIndex = getRandomInt(9)
      const movie = newMovies[randomIndex]
      setMovie(movie)
    })
  }

  return (
    <div>
      <h3>
        {movie.title}
      </h3>
      <button onClick={setNewMovie}>
        Generate New Movie
      </button>
    </div>
  )
}

The problem with this code is that it has to call the API every time I want to set a new movie, which is not ideal since it takes longer to run. What I would like to do is call the API once within the useEffect hook and have the returned movies stay the same throughout the hole lifetime of the component so that I don’t have to pull the list of movies on every render. I’m using useRef to achieve this, here’s what I’ve tryied:

export default function Page() {
  const movies = useRef<Movie[]>([])
  const [movie, setMovie] = useState<Movie>({
    title: ""
  })

  useEffect(() => {
    setNewMovies()
    setNewMovie()
  }, [])

  function setNewMovies() {
    getMoviesRequest().then((newMovies) => {
      movies.current = newMovies
    })
  }
  function setNewMovie() {
    const randomIndex = getRandomInt(9)
    const movie = movies.current[randomIndex]
    setMovie(movie)
  }

  return (
    <div>
      <h3>
        {movie.title}
      </h3>
      <button onClick={setNewMovie}>
        Generate New Movie
      </button>
    </div>
  )
}

This isn’t working since if I try to access the current value of movies outside the promise It seems to not have been updated.

I looked for answers and I found that you can’t really access the value of the promise from outside unless you are on an async function, but I already tryied that and I get the same result.

Is there a way to achieve what I’m trying to do?
Thank you!

2

Answers


  1. The issue you’re facing is that the assignment movies.current = newMovies is asynchronous, so when you try to access the movies.current value immediately after the promise resolves, it might not have been updated yet.

    To ensure that the movies.current value is updated before accessing it, you can modify your code slightly by using the useEffect hook. Here’s an updated version of your code that should work:

    export default function Page() {
      const movies = useRef<Movie[]>([]);
      const [movie, setMovie] = useState<Movie>({ title: "" });
    
      useEffect(() => {
        getMoviesRequest().then((newMovies) => {
          movies.current = newMovies;
          setNewMovie();
        });
      }, []);
    
      function setNewMovie() {
        const randomIndex = getRandomInt(9);
        const movie = movies.current[randomIndex];
        setMovie(movie);
      }
    
      return (
        <div>
          <h3>{movie.title}</h3>
          <button onClick={setNewMovie}>Generate New Movie</button>
        </div>
      );
    }
    

    In this updated version, the movies are fetched in the useEffect hook and stored in the movies ref. Once the promise resolves and the movies.current value is updated, we call the setNewMovie function to select a random movie from the list and update the state.

    Now, when the component renders, the movies will be fetched only once, and subsequent calls to setNewMovie will select a movie from the pre-fetched list, avoiding unnecessary API calls.

    Login or Signup to reply.
  2. The problem with this code

    setNewMovies()
    setNewMovie()
    

    is that setNewMovies is async and you need to wait for it to end till you can do the other action. You could have a method called getMoviesFromCacheOrApi that would either return the ref value or get the movies from api and then store it in ref if the ref value is not set. I would use null as the initial value for the ref because you may have an empty array returned by the api but you should know better what it returns in your case.

    Below is an example of what I wanted to say.

    export default function Page() {
      const cachedMoviesFromApi = useRef<Movie[] | null>(null)
      const [movie, setMovie] = useState<Movie>({
        title: ""
      })
    
      useEffect(() => {
        setNewMovie().then()
      }, [])
    
      async function getMoviesFromCacheOrApi() {
        if (cachedMoviesFromApi.current) {
          return cachedMoviesFromApi.current
        }
    
        const moviesFromApi = await getMoviesRequest()
        cachedMoviesFromApi.current = moviesFromApi
    
        return moviesFromApi
      }
    
      async function setNewMovie() {
        const movies = await getMoviesFromCacheOrApi()
        const randomIndex = getRandomInt(9)
        const movie = movies[randomIndex]
    
        setMovie(movie)
      }
    
      return (
        <div>
          <h3>
            {movie.title}
          </h3>
          <button onClick={setNewMovie}>
            Generate New Movie
          </button>
        </div>
      )
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search