Add frontend for search#116
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite. This stack of pull requests is managed by Graphite. Learn more about stacking. |
94f2524 to
1a000cd
Compare
c6b9f31 to
a0793e6
Compare
| useEffect(() => { | ||
| setLoading(true); | ||
| fetch(`/search?query=${encodeURIComponent(searchQuery)}`) | ||
| .then(response => { | ||
| if (!response.ok) { | ||
| throw new Error('Network response was not ok'); | ||
| } | ||
| return response.json(); | ||
| }) | ||
| .then(data => { | ||
| setTasks(data); | ||
| setLoading(false); | ||
| }) | ||
| .catch(error => { | ||
| setError(error.message); | ||
| setLoading(false); | ||
| }); | ||
| }, [searchQuery]); // Depend on searchQuery |
There was a problem hiding this comment.
Race condition: Multiple fetch requests can be in flight simultaneously as the user types, and responses may arrive out of order. If a user types "cat" then quickly types "s" to make "cats", the response for "cat" might arrive after "cats", displaying wrong results.
Fix by implementing cleanup in useEffect:
useEffect(() => {
const abortController = new AbortController();
setLoading(true);
fetch(`/search?query=${encodeURIComponent(searchQuery)}`, {
signal: abortController.signal
})
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => {
setTasks(data);
setLoading(false);
})
.catch(error => {
if (error.name !== 'AbortError') {
setError(error.message);
setLoading(false);
}
});
return () => abortController.abort();
}, [searchQuery]);| useEffect(() => { | |
| setLoading(true); | |
| fetch(`/search?query=${encodeURIComponent(searchQuery)}`) | |
| .then(response => { | |
| if (!response.ok) { | |
| throw new Error('Network response was not ok'); | |
| } | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| setTasks(data); | |
| setLoading(false); | |
| }) | |
| .catch(error => { | |
| setError(error.message); | |
| setLoading(false); | |
| }); | |
| }, [searchQuery]); // Depend on searchQuery | |
| useEffect(() => { | |
| const abortController = new AbortController(); | |
| setLoading(true); | |
| fetch(`/search?query=${encodeURIComponent(searchQuery)}`, { | |
| signal: abortController.signal | |
| }) | |
| .then(response => { | |
| if (!response.ok) throw new Error('Network response was not ok'); | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| setTasks(data); | |
| setLoading(false); | |
| }) | |
| .catch(error => { | |
| if (error.name !== 'AbortError') { | |
| setError(error.message); | |
| setLoading(false); | |
| } | |
| }); | |
| return () => abortController.abort(); | |
| }, [searchQuery]); // Depend on searchQuery |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
| .catch(error => { | ||
| setError(error.message); | ||
| setLoading(false); | ||
| }); |
There was a problem hiding this comment.
Error state persists across requests. If a request fails and sets an error, then the user types again triggering a new successful request, the old error remains set. After the successful request completes (loading=false), line 32-34 will still display the error message instead of the results.
Fix by clearing error state when starting a new request:
useEffect(() => {
setLoading(true);
setError(null); // Clear previous error
fetch(`/search?query=${encodeURIComponent(searchQuery)}`)
// ... rest of the code
}, [searchQuery]);Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
| useEffect(() => { | ||
| setLoading(true); | ||
| fetch(`/search?query=${encodeURIComponent(searchQuery)}`) | ||
| .then(response => { | ||
| if (!response.ok) { | ||
| throw new Error('Network response was not ok'); | ||
| } | ||
| return response.json(); | ||
| }) | ||
| .then(data => { | ||
| setTasks(data); | ||
| setLoading(false); | ||
| }) | ||
| .catch(error => { | ||
| setError(error.message); | ||
| setLoading(false); | ||
| }); | ||
| }, [searchQuery]); // Depend on searchQuery |
There was a problem hiding this comment.
Performance issue: Every single keystroke triggers an immediate API request. If a user types "javascript" (10 characters), this generates 10 separate API calls. In production, this will cause excessive server load, potential rate limiting, and poor performance.
Fix by implementing debouncing:
useEffect(() => {
const timeoutId = setTimeout(() => {
setLoading(true);
fetch(`/search?query=${encodeURIComponent(searchQuery)}`)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => {
setTasks(data);
setLoading(false);
})
.catch(error => {
setError(error.message);
setLoading(false);
});
}, 300); // 300ms debounce
return () => clearTimeout(timeoutId);
}, [searchQuery]);| useEffect(() => { | |
| setLoading(true); | |
| fetch(`/search?query=${encodeURIComponent(searchQuery)}`) | |
| .then(response => { | |
| if (!response.ok) { | |
| throw new Error('Network response was not ok'); | |
| } | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| setTasks(data); | |
| setLoading(false); | |
| }) | |
| .catch(error => { | |
| setError(error.message); | |
| setLoading(false); | |
| }); | |
| }, [searchQuery]); // Depend on searchQuery | |
| useEffect(() => { | |
| const timeoutId = setTimeout(() => { | |
| setLoading(true); | |
| fetch(`/search?query=${encodeURIComponent(searchQuery)}`) | |
| .then(response => { | |
| if (!response.ok) { | |
| throw new Error('Network response was not ok'); | |
| } | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| setTasks(data); | |
| setLoading(false); | |
| }) | |
| .catch(error => { | |
| setError(error.message); | |
| setLoading(false); | |
| }); | |
| }, 300); // 300ms debounce | |
| return () => clearTimeout(timeoutId); | |
| }, [searchQuery]); // Depend on searchQuery |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.

No description provided.