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 "<maxResults>=<Number>". 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;
};
};
}
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;
}
Here videos is the array video or we can put as Array<Video>. We need to have loading to add some UI while loading, error to handle errors, play video and selected video to handle videos selected to play. Thus we will be returning the above to use later. We also need to set each as a state so we will be passing the state result. Mostly, we will be passing an async function to fetch and call only once using use effect. The loading is initially assume to be true and within the fetch function if we get a response status 200 then we can set loading to false. Here is the final hook implementation.
Youtube API End Point: https://www.googleapis.com/youtube/v3/search?key=${apiKey}&part=snippet&type=video&maxResults=${maxResult},
eEffect, useState } from 'react';
import axios from 'axios';
export interface VideoSnippet {
title: string;
description: string;
thumbnails?: {
default?: {
url: string;
};
medium?: {
url: string;
};
high?: {
url: string;
};
};
}
export interface Video {
id: {
videoId: string;
};
snippet: VideoSnippet;
}
interface UseYoutubeVideosResult {
videos: Video[];
loading: boolean;
error: string | null;
playVideo: (videoId: string) => void;
selectedVideoId: string | null;
}
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,
};
}
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 <number> (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)
}
};
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;
};
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;
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);
we then implement a functio to play videos by calling the deconstructed function from hoo,
function handleVideoClick(videoId: string) {
playVideo(videoId);
}
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>
</>
);
};
The video play section is commented out and not part of this article. Yet, in the <img/> tag we setting the video thumbnails and to default size.
the result we get with dummy data. Note we are only calling the amount of videos based on screen size.
Final Steps:
Now that we verified, we can make a console log where we printing the length after slicing, we can now use the actual API, add some TailwindCss Styles and test it.
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. Soon, I will make these articles also available on Dev Community.