DEV Community

Cover image for Front End Development And Integration With Google API | Using Hooks, Throttle, And Dynamic Style.
Mohamed Sharif
Mohamed Sharif

Posted on

Front End Development And Integration With Google API | Using Hooks, Throttle, And Dynamic Style.

This article can be better understood if you are an intermediate React and familiar with API Calls.

In this article, I would like to go over in how to solve the problem of fetching only the videos that an user can see within the application UI. Basically, the problem is you have to fetch videos in a perfect matrix grid( rows and columns are the same). The challenge is that the number of rows and grids change as screen reduces. Mostly, only videos that can be fit in the grid it should be fetched. With this in mind we are implementing a dynamic UI where only a certain amount of videos are fetched within a screen size. And where do I come with this ? If you open the youtube application and set at full screen at home page you ll notice that the top sub grid have 5 rows and 5 columns and the number of videos change while maintaining an even distribution

Approach To The Problem:
Controlling the number of Videos to fetch And Implementing useYoutubeVideos Hook:
When we call the Youtube API , provided by Google, we are able to add a max limit of videos to fetch by querying with "=". Having this ability to select max we can dynamically change the value to fit our needs. When we calling an API within our application where we expecting more than just saving the data, we can create a hook and in the hook we will handle all the functionality to handle the fetching and returning the state needed.

Now, how are we going to implement the hook. First thing we need to know what is the form of data we are expected to receive. This is a must do and yes I am using Typecript. So, before get into the documentation the most common sense is that we want to return many videos and the a good data structure to store are arrays. Thus, we expecting Videos [] of type Videos but we have no idea what is the type Video. After exploring the documentation we learn that a video is


`{
  id: {
    videoId: string;
  };
  snippet: VideoSnippet;
} `

And Video snipped is 
`{
  title: string;
  description: string;
  thumbnails?: {
    default?: {
      url: string;
    };
    medium?: {
      url: string;
    };
    high?: {
      url: string;
    };
  };
}`
Enter fullscreen mode Exit fullscreen mode

there is nothing complex about the data structure for the api and we are given several options to the video thumbnails.

In addition to this, were are given more parameters such loading and error. Another important functionality is we need to play a video but to play a video we need a function that gets the video id. Now, we can formulate our hook as a function, in this case we need to pass the API key from google console, and max results. But our result will be of

`{
  videos: Video[];
  loading: boolean;
  error: string | null;
  playVideo: (videoId: string) => void;
  selectedVideoId: string | null;
}`
Enter fullscreen mode Exit fullscreen mode

Here videos is the array video or we can put as Array

`useEffect, useState } from 'react';
import axios from 'axios';

export interface VideoSnippet {
  title: string;
  description: string;
  thumbnails?: {
    default?: {
      url: string;
    };
    medium?: {
      url: string;
    };
    high?: {
      url: string;
    };
  };
}
Enter fullscreen mode Exit fullscreen mode
export interface Video {
  id: {
    videoId: string;
  };
  snippet: VideoSnippet;
}
Enter fullscreen mode Exit fullscreen mode
interface UseYoutubeVideosResult {
  videos: Video[];
  loading: boolean;
  error: string | null;
  playVideo: (videoId: string) => void;
  selectedVideoId: string | null;
}


Enter fullscreen mode Exit fullscreen mode

export default function useYoutubeVideos(
  apiKey: string,
  maxResult: number,
): UseYoutubeVideosResult {
  const [videos, setVideos] = useState<Video[]>([]);
  const [loading, setLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);
  const [selectedVideoId, setSelectedVideoId] = useState<string | null>(null);

  function playVideo(videoId: string): void {
    setSelectedVideoId(videoId);
  }

  async function fetchVideos() {
    setLoading(true);
    setError(null);

    try {
      const response = await axios.get(
        `https://www.googleapis.com/youtube/v3/search?key=${apiKey}&part=snippet&type=video&maxResults=${maxResult}`,
      );

      if (response.status === 200) {
        setVideos(response.data.items);
      }
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    fetchVideos();
  }, []);

  return {
    videos,
    loading,
    error,
    playVideo,
    selectedVideoId,
  };
}`
Enter fullscreen mode Exit fullscreen mode

Implementing UseVideoGrid hook:
The idea is to have hook that will check the width of screen and based on the width it will set the state to the number of videos per row. Here is where we would use a throttle function for performance. We will then be returning the state videos per row.

Hook Implementation
The first step is to get the screen width with window.innerWidth and set a basic if and else condition where we will use a state videosPerRow (5) default value set to 5.

const [videosPerRow, setVideosPerRow] = useState<number>(5);

  const determineVideosToShow = () => {
    const screenWidth = window.innerWidth;

    if (screenWidth <= 500) {
      setVideosPerRow(1); // 1 video per row on very small screens (2 rows total)
    } else if (screenWidth > 500 && screenWidth <= 739) {
      setVideosPerRow(2); // 2 videos per row (2 rows total)
    } else if (screenWidth >= 740 && screenWidth <= 1023) {
      setVideosPerRow(3); // 3 videos per row (2 rows total)
    } else if (screenWidth >= 1024 && screenWidth <= 1279) {
      setVideosPerRow(4); // 4 videos per row (2 rows total)
    } else {
      setVideosPerRow(5); // 5 videos per row (2 rows total)
    }
  };
Enter fullscreen mode Exit fullscreen mode

we put the logic inside a nested function so we can call it inside the throttle as a call back function. I have set the time to 150 mile seconds to keep its responsiveness intact, otherwise, it will not be as responsive as needed when interacting with screen size.

 // Adding the throttle function here with 150 seconds time out
  const throttleVideosToShowPerRow  = useThrottle(determineVideosToShow, 150);

import { useEffect, useState } from 'react';
import { useThrottle } from './useThrottle.ts';

/**
 * @videosPerRow{number} return number of videos show per row.
 * @setVideosPerRow{void} function that uses screen width to set number of videos to show
 * @return is the number of videos per row to be used
 */
export const useVideoGrid = () => {
  const [videosPerRow, setVideosPerRow] = useState<number>(5);

  const determineVideosToShow = () => {
    const screenWidth = window.innerWidth;

    if (screenWidth <= 500) {
      setVideosPerRow(1); // 1 video per row on very small screens (2 rows total)
    } else if (screenWidth > 500 && screenWidth <= 739) {
      setVideosPerRow(2); // 2 videos per row (2 rows total)
    } else if (screenWidth >= 740 && screenWidth <= 1023) {
      setVideosPerRow(3); // 3 videos per row (2 rows total)
    } else if (screenWidth >= 1024 && screenWidth <= 1279) {
      setVideosPerRow(4); // 4 videos per row (2 rows total)
    } else {
      setVideosPerRow(5); // 5 videos per row (2 rows total)
    }
  };

  // Adding the throttle function here with 150 seconds time out
  const throttleVideosToShowPerRow = useThrottle(determineVideosToShow, 150);

  // use effect to change the state whenever
  useEffect(() => {
    throttleVideosToShowPerRow();

    const handleVideosToShow = () => {
      throttleVideosToShowPerRow();
    };

    window.addEventListener('resize', handleVideosToShow);

    return () => window.removeEventListener('resize', handleVideosToShow);
  }, [throttleVideosToShowPerRow]);

  return videosPerRow;
};
`
Enter fullscreen mode Exit fullscreen mode

Putting All together

Now, we are going to call these hooks in the component that represents the display for the videos. First, we will use a dummy_data before fetching the api since theres a limit to how much we can request per day.


import dummy_videos from '../../../dummyData.json';
 // Using the useVideo hook to control number of videos show per screen size
  const videosPerRow = useVideoGrid();

  const totalVideosToShow = videosPerRow * 2;
Enter fullscreen mode Exit fullscreen mode

The idea is that we want both rows to have same amount of videos so we multiply by 2 since we are dealing with only two rows

We then call the youtube vidoes use hook and destruct elements we need to use

const { videos, loading, error, playVideo, selectedVideoId } =
  useYoutubeVideos(api_key, totalVideosToShow);


Enter fullscreen mode Exit fullscreen mode

we then implement a functio to play videos by calling the deconstructed function from hoo,


function handleVideoClick(videoId: string) {
  playVideo(videoId);
}

Enter fullscreen mode Exit fullscreen mode

Now we add the dynamic values in the div for "gridTemplateColumns" we basically repeating columns per video per row and set min and max (0 as no videos and 1fr as 1 video. The main div holds 2 inner divs. One div where we have the grid layout for the videos and another div where we map the videos.

return (
    <>
      {/* Main Home Frame */}
      <div className="h-screen overflow-hidden flex justify-center items-start ">
        {!isLoggedIn && <NotLoggedInBanner />}

        {/* first row of videos */}
        <div
          className={` h-[600px] w-full  grid  grid-rows-2  gap-4 p-4  overflow-hidden `}
          style={{
            gridTemplateColumns: `repeat(${videosPerRow},minmax(0,1fr))`,
          }}
        >
          {dummy_videos.videos.slice(0, totalVideosToShow).map((video) => (
            <div
              key={video.id.videoId}
              className="flex  flex-col justify-center items-center rounded-lg border "
            >
              {/*{selectedVideoId === id.videoId ? (*/}
              {/*  <iframe*/}
              {/*    width="560"*/}
              {/*    height="315"*/}
              {/*    src={`https://www.youtube.com/embed/${id.videoId}`}*/}
              {/*    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"*/}
              {/*    allowFullScreen*/}
              {/*    title="YouTube Video Player"*/}
              {/*  ></iframe>*/}
              {/*) : (*/}
              <img
                src={video.snippet.thumbnails?.default?.url}
                alt={video.snippet.title}
                onClick={() => handleVideoClick(video.id.videoId)}
                className="invert pointer"
              />
              {/*)}*/}
              <div className="font-bold text-lg  text-center">
                {video.snippet.title}
              </div>
            </div>
          ))}
        </div>
      </div>
    </>
  );
};

Enter fullscreen mode Exit fullscreen mode

The video play section is commented out and not part of this article. Yet, in the tag we setting the video thumbnails and to default size. Note we are only calling the amount of videos based on screen size.

Image description

Image description

Image description

Alright, this is the end of the article. The functionality can be better and current one is causing some of videos to collapse but using CSS "flex-wrap" and "padding" we are able to fix this issue.

Who Am I

My name is Sharif and I have a B.S degree in computer science. Since graduation, I have been mostly focus on full stack development. In addition, I like to solve algorithms questions like leet code. I have done few assessments by big tech companies and currently in the interview process, being as positive as I can. My next academic step in a near future is focus on machine learning since I am strong believer of using it to create apps with a vast arrays of functionalities. This article is part of my personal project where I am building a full stack clone of youtube. If you like my article please give me thumbs up.

Lets Connect On Linkedin

Top comments (0)