From 235124b59f25f8bcb99ce030305985426095c4c5 Mon Sep 17 00:00:00 2001 From: Chetna Singh Date: Wed, 30 Jul 2025 11:12:11 +0530 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Optimize=20fetchData:=20use=20P?= =?UTF-8?q?romise.all=20and=20safe=20pagination?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useGitHubData.ts | 53 ++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/hooks/useGitHubData.ts b/src/hooks/useGitHubData.ts index 9add583..d7a0ba4 100644 --- a/src/hooks/useGitHubData.ts +++ b/src/hooks/useGitHubData.ts @@ -6,20 +6,27 @@ export const useGitHubData = (octokit) => { const [loading, setLoading] = useState(false); const [error, setError] = useState(''); - const fetchAll = async (url, params) => { - let page = 1; - let results = []; - let hasMore = true; - - while (hasMore) { - const response = await octokit.request(url, { ...params, page }); - results = results.concat(response.data.items); - hasMore = response.data.items.length === 100; - page++; - } + // Helper to fetch multiple pages from REST endpoint (not search API) +const fetchPaginated = async (url, params = {}) => { + let page = 1; + const results = []; - return results; - }; + while (true) { + const { data } = await octokit.request(url, { + ...params, + per_page: 100, + page, + }); + + const items = Array.isArray(data.items) ? data.items : []; + results.push(...items); // ✅ safe spreading + + if (items.length < 100) break; + page++; + } + + return results; +}; const fetchData = useCallback(async (username) => { if (!octokit || !username) return; @@ -28,25 +35,27 @@ export const useGitHubData = (octokit) => { setError(''); try { - const [issuesResponse, prsResponse] = await Promise.all([ - fetchAll('GET /search/issues', { + console.time('fetchData'); + + const [issuesList, prsList] = await Promise.all([ + fetchPaginated('GET /search/issues', { q: `author:${username} is:issue`, sort: 'created', order: 'desc', - per_page: 100, }), - fetchAll('GET /search/issues', { + fetchPaginated('GET /search/issues', { q: `author:${username} is:pr`, sort: 'created', order: 'desc', - per_page: 100, }), ]); - setIssues(issuesResponse); - setPrs(prsResponse); + setIssues(issuesList); + setPrs(prsList); + + console.timeEnd('fetchData'); } catch (err) { - setError(err.message); + setError(err.message || 'Something went wrong while fetching GitHub data.'); } finally { setLoading(false); } @@ -59,4 +68,4 @@ export const useGitHubData = (octokit) => { error, fetchData, }; -}; \ No newline at end of file +}; From 94debb3e1afa9e4e7d7d3a918b8cfd9e600f3a25 Mon Sep 17 00:00:00 2001 From: Mehul Date: Wed, 30 Jul 2025 17:13:53 +0530 Subject: [PATCH 2/2] fixed fetch timing --- src/hooks/useGitHubAuth.ts | 2 +- src/hooks/useGitHubData.ts | 86 +++++---- src/hooks/usePagination.ts | 21 --- src/pages/Home/Home.tsx | 354 +++++++++++++------------------------ 4 files changed, 166 insertions(+), 297 deletions(-) delete mode 100644 src/hooks/usePagination.ts diff --git a/src/hooks/useGitHubAuth.ts b/src/hooks/useGitHubAuth.ts index 4ac6139..ad3ce52 100644 --- a/src/hooks/useGitHubAuth.ts +++ b/src/hooks/useGitHubAuth.ts @@ -20,4 +20,4 @@ export const useGitHubAuth = () => { setError, getOctokit, }; -}; \ No newline at end of file +}; diff --git a/src/hooks/useGitHubData.ts b/src/hooks/useGitHubData.ts index d7a0ba4..e9b15d0 100644 --- a/src/hooks/useGitHubData.ts +++ b/src/hooks/useGitHubData.ts @@ -1,69 +1,63 @@ import { useState, useCallback } from 'react'; -export const useGitHubData = (octokit) => { +export const useGitHubData = (octokit: any) => { + const [issues, setIssues] = useState([]); const [prs, setPrs] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); + const [totalIssues, setTotalIssues] = useState(0); + const [totalPrs, setTotalPrs] = useState(0); - // Helper to fetch multiple pages from REST endpoint (not search API) -const fetchPaginated = async (url, params = {}) => { - let page = 1; - const results = []; + const fetchPaginated = async (username: string, type: string, page = 1, per_page = 10) => { - while (true) { - const { data } = await octokit.request(url, { - ...params, - per_page: 100, + const q = `author:${username} is:${type}`; + const response = await octokit.request('GET /search/issues', { + q, + sort: 'created', + order: 'desc', + per_page, page, }); - const items = Array.isArray(data.items) ? data.items : []; - results.push(...items); // ✅ safe spreading - - if (items.length < 100) break; - page++; - } - - return results; -}; - - const fetchData = useCallback(async (username) => { - if (!octokit || !username) return; + return { + items: response.data.items, + total: response.data.total_count, + }; + }; - setLoading(true); - setError(''); + const fetchData = useCallback( + async (username: string, page = 1, perPage = 10) => { - try { - console.time('fetchData'); + if (!octokit || !username) return; - const [issuesList, prsList] = await Promise.all([ - fetchPaginated('GET /search/issues', { - q: `author:${username} is:issue`, - sort: 'created', - order: 'desc', - }), - fetchPaginated('GET /search/issues', { - q: `author:${username} is:pr`, - sort: 'created', - order: 'desc', - }), - ]); + setLoading(true); + setError(''); - setIssues(issuesList); - setPrs(prsList); + try { + const [issueRes, prRes] = await Promise.all([ + fetchPaginated(username, 'issue', page, perPage), + fetchPaginated(username, 'pr', page, perPage), + ]); - console.timeEnd('fetchData'); - } catch (err) { - setError(err.message || 'Something went wrong while fetching GitHub data.'); - } finally { - setLoading(false); - } - }, [octokit]); + setIssues(issueRes.items); + setPrs(prRes.items); + setTotalIssues(issueRes.total); + setTotalPrs(prRes.total); + } catch (err: any) { + setError(err.message); + } finally { + setLoading(false); + } + }, + [octokit] + ); return { issues, prs, + totalIssues, + totalPrs, loading, error, fetchData, diff --git a/src/hooks/usePagination.ts b/src/hooks/usePagination.ts deleted file mode 100644 index 4c9bd2d..0000000 --- a/src/hooks/usePagination.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useState } from 'react'; - -export const usePagination = (rowsPerPage = 10) => { - const [page, setPage] = useState(0); - const [itemsPerPage] = useState(rowsPerPage); - - const handleChangePage = (event, newPage) => { - setPage(newPage); - }; - - const paginateData = (data) => { - return data.slice(page * itemsPerPage, page * itemsPerPage + itemsPerPage); - }; - - return { - page, - itemsPerPage, - handleChangePage, - paginateData, - }; -}; \ No newline at end of file diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index d01792c..c7907ff 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Container, Box, @@ -24,11 +24,9 @@ import { } from "@mui/material"; import { useGitHubAuth } from "../../hooks/useGitHubAuth"; import { useGitHubData } from "../../hooks/useGitHubData"; -import { usePagination } from "../../hooks/usePagination"; const ROWS_PER_PAGE = 10; -// Define the shape of the data received from GitHub interface GitHubItem { id: number; title: string; @@ -40,109 +38,88 @@ interface GitHubItem { } const Home: React.FC = () => { - // Hooks for managing user authentication - const { - username, - setUsername, - token, - setToken, - error: authError, - getOctokit, - } = useGitHubAuth(); - + const { username, setUsername, token, setToken, error: authError, getOctokit } = useGitHubAuth(); const octokit = getOctokit(); - const { - issues, - prs, - loading, - error: dataError, - fetchData, - } = useGitHubData(octokit); - - const { page, itemsPerPage, handleChangePage, paginateData } = - usePagination(ROWS_PER_PAGE); + const {issues, prs, totalIssues, totalPrs, loading, error: dataError, fetchData} = useGitHubData(octokit); - // State for various filters and tabs const [tab, setTab] = useState(0); - const [issueFilter, setIssueFilter] = useState("all"); - const [prFilter, setPrFilter] = useState("all"); - const [searchTitle, setSearchTitle] = useState(""); - const [selectedRepo, setSelectedRepo] = useState(""); - const [startDate, setStartDate] = useState(""); - const [endDate, setEndDate] = useState(""); + const [page, setPage] = useState(0); + + const [issueFilter, setIssueFilter] = useState("all"); + const [prFilter, setPrFilter] = useState("all"); + const [searchTitle, setSearchTitle] = useState(""); + const [selectedRepo, setSelectedRepo] = useState(""); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + + // Fetch data when tab or page changes + useEffect(() => { + if (username) { + fetchData(username, page + 1, ROWS_PER_PAGE); + } + }, [tab, page]); - // Handle data submission to fetch GitHub data - const handleSubmit = (e: React.FormEvent): void => { + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - fetchData(username); + setPage(0); + fetchData(username, 1, ROWS_PER_PAGE); }; - // Format date strings into a readable format - const formatDate = (dateString: string): string => { - return new Date(dateString).toLocaleDateString(); + const handlePageChange = (_: unknown, newPage: number) => { + setPage(newPage); }; - // Filter data based on selected criteria - const filterData = ( - data: GitHubItem[], - filterType: string - ): GitHubItem[] => { - let filteredData = [...data]; + const formatDate = (dateString: string) => + new Date(dateString).toLocaleDateString(); - if (filterType === "open" || filterType === "closed" || filterType === "merged") { - filteredData = filteredData.filter((item) => - filterType === "merged" + const filterData = (data: GitHubItem[], type: string): GitHubItem[] => { + let filtered = [...data]; + + if (type === "open" || type === "closed" || type === "merged") { + filtered = filtered.filter((item) => + type === "merged" ? item.pull_request?.merged_at - : item.state === filterType + : item.state === type ); } if (searchTitle) { - filteredData = filteredData.filter((item) => + filtered = filtered.filter((item) => item.title.toLowerCase().includes(searchTitle.toLowerCase()) ); } if (selectedRepo) { - filteredData = filteredData.filter((item) => + filtered = filtered.filter((item) => item.repository_url.includes(selectedRepo) ); } if (startDate) { - filteredData = filteredData.filter( + filtered = filtered.filter( (item) => new Date(item.created_at) >= new Date(startDate) ); } + if (endDate) { - filteredData = filteredData.filter( + filtered = filtered.filter( (item) => new Date(item.created_at) <= new Date(endDate) ); } - return filteredData; + return filtered; }; - // Determine the current tab's data - const currentData = - tab === 0 ? filterData(issues, issueFilter) : filterData(prs, prFilter); + const currentRawData = tab === 0 ? issues : prs; + const currentFilteredData = filterData(currentRawData, tab === 0 ? issueFilter : prFilter); + const totalCount = tab === 0 ? totalIssues : totalPrs; - // Paginate the filtered data - const displayData = paginateData(currentData); - - // Main UI rendering return ( - - {/* Authentication Form */} + + + {/* Auth Form */} +
{ required sx={{ flex: 1 }} /> -
+
- {/* Filters Section */} - - {/* Search Title */} - setSearchTitle(e.target.value)} - sx={{ - flexBasis: { xs: "100%", sm: "100%", md: "48%", lg: "23%" }, - flexGrow: 1, - }} - /> - - {/* Repository */} - setSelectedRepo(e.target.value)} - sx={{ - flexBasis: { xs: "100%", sm: "100%", md: "48%", lg: "23%" }, - flexGrow: 1, - }} - /> - - {/* Start Date */} - setStartDate(e.target.value)} - InputLabelProps={{ shrink: true }} - sx={{ - flexBasis: { xs: "100%", sm: "100%", md: "48%", lg: "23%" }, - flexGrow: 1, - }} - /> - - {/* End Date */} - setEndDate(e.target.value)} - InputLabelProps={{ shrink: true }} - sx={{ - flexBasis: { xs: "100%", sm: "100%", md: "48%", lg: "23%" }, - flexGrow: 1, - }} - /> - - - -{/* Tabs and State Dropdown */} - - setTab(newValue)} - variant="scrollable" - scrollButtons="auto" - sx={{ flexGrow: 1, minWidth: "200px" }} - > - - - - - - State - - - - - -{/* Error Alert */} -{(authError || dataError) && ( - - {authError || dataError} - -)} - -{/* Table Section */} -{loading ? ( - - - -) : ( - - - - - - - Title - Repository - State - Created - - - - {displayData.map((item: GitHubItem) => ( - - - - {item.title} - - - - {item.repository_url.split("/").slice(-1)[0]} - - - {item.pull_request?.merged_at ? "merged" : item.state} - - {formatDate(item.created_at)} - - ))} - -
- -
-
+ {/* Filters */} + + setSearchTitle(e.target.value)} /> + setSelectedRepo(e.target.value)} /> + setStartDate(e.target.value)} InputLabelProps={{ shrink: true }} /> + setEndDate(e.target.value)} InputLabelProps={{ shrink: true }} /> + + + {/* Tabs + State Filter */} + + { setTab(v); setPage(0); }}> + + + + + State + + + + + {(authError || dataError) && ( + + {authError || dataError} + + )} + {loading ? ( + + + + ) : ( + + + + + + Title + Repository + State + Created + + + + {currentFilteredData.map((item) => ( + + + + {item.title} + + + + {item.repository_url.split("/").slice(-1)[0]} + + + {item.pull_request?.merged_at ? "merged" : item.state} + + {formatDate(item.created_at)} + + ))} + +
+ +
)}