-
Notifications
You must be signed in to change notification settings - Fork 0
β‘ Bolt: Correct talks search debounce logic to prevent excess URL updates #133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| "use client"; | ||
|
|
||
| import { usePathname, useRouter, useSearchParams } from "next/navigation"; | ||
| import { useCallback, useEffect, useState, useTransition } from "react"; | ||
| import { useCallback, useEffect, useRef, useState, useTransition } from "react"; | ||
| import { motion } from "framer-motion"; | ||
|
|
||
| interface TalksFilterBarProps { | ||
|
|
@@ -11,6 +11,7 @@ interface TalksFilterBarProps { | |
|
|
||
| export default function TalksFilterBar({ tracks, year: _year }: TalksFilterBarProps) { | ||
| const router = useRouter(); | ||
| const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null); | ||
|
|
||
|
Comment on lines
+14
to
15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: #!/bin/bash
set -euo pipefail
echo "Inspect debounce lifecycle in TalksFilterBar:"
rg -n "handleTrackClick|handleSearchChange|clearTimeout\\(debounceRef.current\\)|setTimeout\\(" components/layout/TalksFilterBar.tsx -C2
echo
echo "Inspect tests for 'type then click track before debounce expires':"
rg -n "fireEvent\\.change|fireEvent\\.click|advanceTimersByTime" __tests__/components/layout/TalksFilterBar.test.tsx -C3Repository: anyulled/devbcn-nextjs Length of output: 2332 π Script executed: cat -n components/layout/TalksFilterBar.tsx | head -100Repository: anyulled/devbcn-nextjs Length of output: 3907 π Script executed: cat -n components/layout/TalksFilterBar.tsx | tail -80Repository: anyulled/devbcn-nextjs Length of output: 3394 π Script executed: cat -n __tests__/components/layout/TalksFilterBar.test.tsxRepository: anyulled/devbcn-nextjs Length of output: 2468 Clear pending debounce before non-debounced track clicks to prevent stale URL rewrites. A pending timeout from Additionally, the component lacks cleanup for the debounce timeout on unmount, which allows the timer to fire after the component is destroyed. The following fixes are required:
Proposed fix@@
const handleTrackClick = (track: string) => {
+ if (debounceRef.current) {
+ clearTimeout(debounceRef.current);
+ debounceRef.current = null;
+ }
const newTrack = track === selectedTrack ? "" : track;
setSelectedTrack(newTrack);
updateFilters(newTrack, searchQuery);
};
@@
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@@
debounceRef.current = setTimeout(() => {
updateFilters(selectedTrack, newQuery);
+ debounceRef.current = null;
}, 300);
};
+
+ useEffect(() => {
+ return () => {
+ if (debounceRef.current) {
+ clearTimeout(debounceRef.current);
+ }
+ };
+ }, []);π€ Prompt for AI Agents |
||
| const formatTrackName = (track: string) => { | ||
| return track.split("(")[0].trim(); | ||
|
|
@@ -60,11 +61,16 @@ export default function TalksFilterBar({ tracks, year: _year }: TalksFilterBarPr | |
| const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| const newQuery = e.target.value; | ||
| setSearchQuery(newQuery); | ||
|
|
||
| // Clear the previous timeout if it exists | ||
| if (debounceRef.current) { | ||
| clearTimeout(debounceRef.current); | ||
| } | ||
|
|
||
| // Debounce the URL update for search | ||
| const timeoutId = setTimeout(() => { | ||
| debounceRef.current = setTimeout(() => { | ||
| updateFilters(selectedTrack, newQuery); | ||
| }, 300); | ||
|
Comment on lines
+71
to
73
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implementation correctly debounces the search input. However, it introduces a potential race condition. The For example:
A simple way to fix this is to cancel the pending search timeout whenever a track is changed. This would require a small modification to the const handleTrackClick = (track: string) => {
if (debounceRef.current) {
clearTimeout(debounceRef.current);
}
// ... rest of the function
};Since
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The debounce delay For example: const DEBOUNCE_DELAY_MS = 300;
// ...
debounceRef.current = setTimeout(() => {
updateFilters(selectedTrack, newQuery);
}, DEBOUNCE_DELAY_MS); |
||
| return () => clearTimeout(timeoutId); | ||
| }; | ||
|
|
||
| return ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The introduction of
useReffor manual timeout management is a good fix for the debouncing logic. However, this manual approach requires careful handling of the component lifecycle. A timeout scheduled withsetTimeoutwill execute even if the component unmounts, which can lead to React warnings about state updates on unmounted components or memory leaks.To prevent this, you should clean up the pending timeout in a
useEffecthook. This ensures that if the component is unmounted, the timeout is cleared.Please add this effect to the component to make it more robust.