I am struggling to understand exactly what is going on with the Youtube API.
It seems the application I developed in React continuously makes requests to the Youtube API even after initial render is done. I had to go back and refresh my understanding of useEffect and realized that it was set up correctly and then even after I implemented a way to cache the initial requests, it seems to still be making requests from what I can tell in the network tab and obviously thats eating up my quota.
I started to notice it after I developed a custom hook to store my fetching of 8 very specific videos from YouTube. This is my useVideos
custom hook:
import { useState, useEffect } from "react";
import youtube from "../apis/youtube";
const useVideos = () => {
const [videos, setVideos] = useState(() => {
const cachedVideos = localStorage.getItem("cachedVideos");
return cachedVideos ? JSON.parse(cachedVideos) : [];
});
useEffect(() => {
if (videos.length === 0) {
fetchSpecificVideos();
}
}, []);
const fetchSpecificVideos = async () => {
try {
const videoIds = [
"VIDEO_1",
"VIDEO_2",
"VIDEO_3",
"VIDEO_4",
"VIDEO_5",
"VIDEO_6",
"VIDEO_7",
"VIDEO_8",
];
const videoData = [];
for (const videoId of videoIds) {
const response = await youtube.get("/videos", {
params: {
id: videoId,
part: "snippet",
maxResults: 8,
},
});
if (response.data.items && response.data.items.length > 0) {
videoData.push(response.data.items[0]);
}
}
setVideos(videoData);
localStorage.setItem("cachedVideos", JSON.stringify(videoData));
} catch (error) {
console.error("Error fetching videos:", error);
}
};
return [videos, fetchSpecificVideos];
};
export default useVideos;
NOTE: I did replace the actual ids with what you see above.
In my actual api file I have the following:
import axios from "axios";
const KEY = "my-youtube-api-key";
export default axios.create({
baseURL: "https://www.googleapis.com/youtube/v3",
params: {
part: "snippet",
maxResults: 8,
key: KEY,
},
});
I am adding a screenshot of the network tab, perhaps I am misunderstanding what is going on, but to me that looks like a lot of requests for just 8 very specific videos that load once on app render. This is happening with or without the localStorage
implementation:
I am wondering if this is a case for some type of memoization.
A colleague suggested that if multiple (N) components are using the custom hook of useVideo
then all of those components will initiate the asynchronous request, that makes sense, however, only the parent App component is making use of that custom hook. I am posting the App.js component here:
import React, { useState, useEffect } from "react";
import Header from "./Header";
import VideoList from "./VideoList";
import VideoDetail from "./VideoDetail";
import useVideos from "../hooks/useVideos";
const App = () => {
const [selectedVideo, setSelectedVideo] = useState(null);
const [videos, fetchSpecificVideos] = useVideos();
useEffect(() => {
if (videos.length === 0) {
fetchSpecificVideos();
}
}, [videos, fetchSpecificVideos]);
useEffect(() => {
if (videos.length > 0) {
setSelectedVideo(videos[0]);
}
}, [videos]);
return (
<div className="ui container">
<div className="ui inverted menu">
<Header />
</div>
<VideoList
onVideoSelect={setSelectedVideo}
videos={videos}
selectedVideo={selectedVideo}
/>
<VideoDetail video={selectedVideo} />
</div>
);
};
export default App;
The custom hook is not being utilized in any other components.
2
Answers
update the useEffect code
Answer based on latest code edits:
The most critical part:
fetchSpecificVideos
is not declared withuseCallback
hook and used in auseEffect dependency array
of App component. Because of that – every time the App component is rerendered due to some state changed –fetchSpecificVideos
function is recreated with newreference
and that forcesuseEffect
to re-execute.Fix – wrap
fetchSpecificVideos
withuseCallback
:Next issue – you have a
useEffect
to callfetchSpecificVideos
if videos.length === 0 both inside the hook and inside App component which is.. redundant and eats your actual quota.Generic recommendation – everything that is "exported" by the hook or context should be wrapped with
useState
oruseCallback
.Also I’ll duplicate my comment here for future readers:
if localstorage is empty, on user first visit, and multiple (N) components are using your hook, then all those components will initiale (N * videos_count) asynchronous requests to youtube api, and only after this async operation is done – all of them will start writing that fetched data into localstorage and only then this data is "cached". Suggestion is to add a top level Context that will load and keep the data and then just consume that data via hook or useContext.