Skip to content

Commit 09cf09d

Browse files
committed
Optimize search request handling with debouncing and AbortController
1 parent 6c6bc3e commit 09cf09d

2 files changed

Lines changed: 72 additions & 12 deletions

File tree

src/hooks/useGitHubData.ts

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useCallback, useRef } from 'react';
1+
import { useState, useCallback, useRef, useEffect } from 'react';
22
import { Octokit } from '@octokit/core';
33

44
interface GitHubItem {
@@ -34,14 +34,16 @@ export const useGitHubData = (
3434

3535
// Prevent stale responses overwriting latest data
3636
const lastRequestId = useRef(0);
37+
const abortControllerRef = useRef<AbortController | null>(null);
3738

3839
const fetchPaginated = async (
3940
octokit: Octokit,
4041
username: string,
4142
type: 'issue' | 'pr',
4243
page = 1,
4344
perPage = 10,
44-
filters: FetchFilters = {}
45+
filters: FetchFilters = {},
46+
signal?: AbortSignal
4547
) => {
4648
let q = `author:${username} is:${type}`;
4749

@@ -77,6 +79,9 @@ export const useGitHubData = (
7779
order: 'desc',
7880
per_page: perPage,
7981
page,
82+
request: {
83+
signal,
84+
},
8085
}
8186
);
8287

@@ -100,6 +105,14 @@ export const useGitHubData = (
100105
return;
101106
}
102107

108+
// Cancel any active in-flight request before triggering a new one
109+
if (abortControllerRef.current) {
110+
abortControllerRef.current.abort();
111+
}
112+
113+
const controller = new AbortController();
114+
abortControllerRef.current = controller;
115+
103116
const requestId = ++lastRequestId.current;
104117

105118
setLoading(true);
@@ -122,7 +135,8 @@ export const useGitHubData = (
122135
'issue',
123136
page,
124137
perPage,
125-
filters
138+
filters,
139+
controller.signal
126140
)
127141
);
128142
}
@@ -135,15 +149,16 @@ export const useGitHubData = (
135149
'pr',
136150
page,
137151
perPage,
138-
filters
152+
filters,
153+
controller.signal
139154
)
140155
);
141156
}
142157

143158
const results = await Promise.allSettled(requests);
144159

145-
// Ignore stale requests
146-
if (requestId !== lastRequestId.current) {
160+
// Ignore stale or aborted requests
161+
if (requestId !== lastRequestId.current || abortControllerRef.current !== controller) {
147162
return;
148163
}
149164

@@ -156,6 +171,10 @@ export const useGitHubData = (
156171
setIssues(issueResult.value.items);
157172
setTotalIssues(issueResult.value.total);
158173
} else {
174+
const reason = issueResult.reason;
175+
if (reason && reason.name === 'AbortError') {
176+
return;
177+
}
159178
setIssues([]);
160179
setTotalIssues(0);
161180
}
@@ -170,6 +189,10 @@ export const useGitHubData = (
170189
setPrs(prResult.value.items);
171190
setTotalPrs(prResult.value.total);
172191
} else {
192+
const reason = prResult.reason;
193+
if (reason && reason.name === 'AbortError') {
194+
return;
195+
}
173196
setPrs([]);
174197
setTotalPrs(0);
175198
}
@@ -180,22 +203,33 @@ export const useGitHubData = (
180203
);
181204

182205
if (hasRejected) {
206+
const wasAborted = results.some(
207+
(result) => result.status === 'rejected' && result.reason?.name === 'AbortError'
208+
);
209+
if (wasAborted) {
210+
return;
211+
}
183212
setError(
184213
'Some GitHub data could not be fetched completely.'
185214
);
186215
}
187216

188217
setRateLimited(false);
189218
} catch (err: unknown) {
190-
if (requestId !== lastRequestId.current) {
219+
if (requestId !== lastRequestId.current || abortControllerRef.current !== controller) {
191220
return;
192221
}
193222

194223
const error = err as {
195224
status?: number;
196225
message?: string;
226+
name?: string;
197227
};
198228

229+
if (error.name === 'AbortError') {
230+
return;
231+
}
232+
199233
const errorMessage =
200234
error.message?.toLowerCase() || '';
201235

@@ -231,14 +265,23 @@ export const useGitHubData = (
231265
);
232266
}
233267
} finally {
234-
if (requestId === lastRequestId.current) {
268+
if (requestId === lastRequestId.current && abortControllerRef.current === controller) {
235269
setLoading(false);
236270
}
237271
}
238272
},
239273
[getOctokit, rateLimited]
240274
);
241275

276+
// Cleanup abort controller on component unmount
277+
useEffect(() => {
278+
return () => {
279+
if (abortControllerRef.current) {
280+
abortControllerRef.current.abort();
281+
}
282+
};
283+
}, []);
284+
242285
return {
243286
issues,
244287
prs,

src/pages/Tracker/Tracker.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,33 @@ const Home: React.FC = () => {
7979
const [startDate, setStartDate] = useState("");
8080
const [endDate, setEndDate] = useState("");
8181

82-
// Fetch data when username, tab, or page changes
82+
const [debouncedUsername, setDebouncedUsername] = useState(username);
83+
84+
useEffect(() => {
85+
if (!username) {
86+
setDebouncedUsername("");
87+
return;
88+
}
89+
const handler = setTimeout(() => {
90+
setDebouncedUsername(username);
91+
}, 500);
92+
93+
return () => {
94+
clearTimeout(handler);
95+
};
96+
}, [username]);
97+
98+
// Fetch data when debouncedUsername, tab, or page changes
8399
useEffect(() => {
84-
if (username) {
85-
fetchData(username, page + 1, ROWS_PER_PAGE);
100+
if (debouncedUsername && debouncedUsername.trim().length >= 1) {
101+
fetchData(debouncedUsername, page + 1, ROWS_PER_PAGE);
86102
}
87-
}, [tab, page]);
103+
}, [debouncedUsername, tab, page]);
88104

89105
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
90106
e.preventDefault();
91107
setPage(0);
108+
setDebouncedUsername(username);
92109
fetchData(username, 1, ROWS_PER_PAGE);
93110
};
94111

0 commit comments

Comments
 (0)