Summary
After clicking either "Clear All" button on the Practice Page, active filter chips (e.g., the search badge Active: "foo") remain visible for approximately 300 milliseconds before disappearing. This creates a visual flicker that makes the UI feel unresponsive and inconsistent — the user clicks the button, the list resets, but the chip badges linger briefly before going away.
This is a pre-existing UX defect, discovered during the review of PR #198 (which fixed the related crash ReferenceError: setDebouncedSearch is not defined).
Root Cause
In frontend/src/pages/PracticePage.jsx, the computed variable hasFilters — which controls the visibility of the active filter chip section — reads from debouncedSearch instead of searchQuery:
// Current (problematic)
const hasFilters = debouncedSearch || selectedTags.length > 0 || ratingIdx !== 0 || sortBy !== "rating-asc";
debouncedSearch is produced by the useDebounce hook (frontend/src/hooks/useDebounce.js) with a 300ms delay. It only updates after the timer fires. So when clearAll() calls setSearchQuery(""):
searchQuery becomes `""" immediately — the input field clears ✅
debouncedSearch stays at the old value for ~300ms ⚠️
- Because
hasFilters depends on debouncedSearch, the chip section remains rendered for that 300ms window ⚠️
- After 300ms,
debouncedSearch catches up, hasFilters becomes falsy, and the chips vanish
This is semantically incorrect: hasFilters is a UI visibility gate for what the user has entered, not a gate for what query is currently being debounced to the filter logic. The two should not be conflated.
Why This Matters for the Codebase
- Inconsistency: Other filter states (
selectedTags, ratingIdx, sortBy) reset and hide their chips instantly because they are not debounced. Only the search chip has this lag, creating an uneven UX.
- Semantic incorrectness: Using a debounced value to drive immediate UI visibility is architecturally wrong.
debouncedSearch exists to throttle expensive operations (e.g., API/filter calls), not to gate chip visibility.
- User confusion: The user may click "Clear All" again, thinking the first click did not register, because the chip is still showing.
Historical Context
During issue #161, an alternative approach was discussed: modifying useDebounce to return a [value, setter] tuple so the debounced value could be manually reset to "" in clearAll(). That approach was explicitly deferred to a separate issue because it adds hook API complexity and is unnecessary — the chip flicker can be fixed entirely at the component level without touching the hook.
Acceptance Criteria
Files to Investigate
frontend/src/pages/PracticePage.jsx — specifically the hasFilters computed variable and where it controls chip visibility
frontend/src/hooks/useDebounce.js — for context on what debouncedSearch represents (read-only derived value, not a UI state)
References
/cc @kunalverma2512
Summary
After clicking either "Clear All" button on the Practice Page, active filter chips (e.g., the search badge
Active: "foo") remain visible for approximately 300 milliseconds before disappearing. This creates a visual flicker that makes the UI feel unresponsive and inconsistent — the user clicks the button, the list resets, but the chip badges linger briefly before going away.This is a pre-existing UX defect, discovered during the review of PR #198 (which fixed the related crash
ReferenceError: setDebouncedSearch is not defined).Root Cause
In
frontend/src/pages/PracticePage.jsx, the computed variablehasFilters— which controls the visibility of the active filter chip section — reads fromdebouncedSearchinstead ofsearchQuery:debouncedSearchis produced by theuseDebouncehook (frontend/src/hooks/useDebounce.js) with a 300ms delay. It only updates after the timer fires. So whenclearAll()callssetSearchQuery(""):searchQuerybecomes `""" immediately — the input field clears ✅debouncedSearchstays at the old value for ~300mshasFiltersdepends ondebouncedSearch, the chip section remains rendered for that 300ms windowdebouncedSearchcatches up,hasFiltersbecomes falsy, and the chips vanishThis is semantically incorrect:
hasFiltersis a UI visibility gate for what the user has entered, not a gate for what query is currently being debounced to the filter logic. The two should not be conflated.Why This Matters for the Codebase
selectedTags,ratingIdx,sortBy) reset and hide their chips instantly because they are not debounced. Only the search chip has this lag, creating an uneven UX.debouncedSearchexists to throttle expensive operations (e.g., API/filter calls), not to gate chip visibility.Historical Context
During issue #161, an alternative approach was discussed: modifying
useDebounceto return a[value, setter]tuple so the debounced value could be manually reset to""inclearAll(). That approach was explicitly deferred to a separate issue because it adds hook API complexity and is unnecessary — the chip flicker can be fixed entirely at the component level without touching the hook.Acceptance Criteria
Files to Investigate
frontend/src/pages/PracticePage.jsx— specifically thehasFilterscomputed variable and where it controls chip visibilityfrontend/src/hooks/useDebounce.js— for context on whatdebouncedSearchrepresents (read-only derived value, not a UI state)References
/cc @kunalverma2512