skip to Main Content

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:

enter image description here

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


  1. update the useEffect code

    useEffect(() => {
        if (videos.length === 0) {
          fetchSpecificVideos();
        }
      }, [videos.length]);
    
    Login or Signup to reply.
  2. Answer based on latest code edits:

    The most critical part: fetchSpecificVideos is not declared with useCallback hook and used in a useEffect 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 new reference and that forces useEffect to re-execute.

    Fix – wrap fetchSpecificVideos with useCallback:

    const fetchSpecificVideos = useCallback(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);
      }
    }, []);
    

    Next issue – you have a useEffect to call fetchSpecificVideos 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 or useCallback.

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search