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
The issue you’re facing is that the assignment
movies.current = newMovies
is asynchronous, so when you try to access themovies.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 theuseEffect
hook. Here’s an updated version of your code that should work:In this updated version, the movies are fetched in the
useEffect
hook and stored in themovies
ref. Once the promise resolves and themovies.current
value is updated, we call thesetNewMovie
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.The problem with this code
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 calledgetMoviesFromCacheOrApi
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 usenull
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.