Skip to content

UX: Active filter chips flicker/persist ~300ms after clicking "Clear All" on Practice Page #205

@coderabbitai

Description

@coderabbitai

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(""):

  1. searchQuery becomes `""" immediately — the input field clears ✅
  2. debouncedSearch stays at the old value for ~300ms ⚠️
  3. Because hasFilters depends on debouncedSearch, the chip section remains rendered for that 300ms window ⚠️
  4. 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

  • Clicking "Clear All" (the inline chip button) causes the active filter chip section to disappear immediately, with no visible delay
  • Clicking "Clear All Filters" (the empty-results button) causes the same immediate disappearance
  • Normal debounce behavior during typing in the search box is unaffected — the filter/list results still debounce correctly at 300ms
  • No regression in other filter chip states (tags, rating, sort)
  • No console errors

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions