Skip to content

⚡ Bolt: Correct talks search debounce logic to prevent excess URL updates#133

Open
anyulled wants to merge 1 commit intomainfrom
bolt-fix-debounce-15001196132622939092
Open

⚡ Bolt: Correct talks search debounce logic to prevent excess URL updates#133
anyulled wants to merge 1 commit intomainfrom
bolt-fix-debounce-15001196132622939092

Conversation

@anyulled
Copy link
Copy Markdown
Owner

@anyulled anyulled commented Mar 29, 2026

💡 What: Corrected debounce logic for search input by using useRef to store and clear previous timeouts.
🎯 Why: The previous implementation returned the cleanup function from an event handler (which React ignores) and did not track the timeout ID across renders. This resulted in the router updating state for every single key press (N times) rather than just once after typing stopped, causing unnecessary re-renders and potential performance issues with React transitions.
📊 Impact: Prevents N router pushes when typing N characters quickly. Significantly reduces layout thrashing during searches.
🔬 Measurement: Typing fast into the talks search bar no longer creates multiple staggered UI updates or URL changes.


PR created automatically by Jules for task 15001196132622939092 started by @anyulled

Summary by CodeRabbit

  • Bug Fixes

    • Fixed search input debouncing in TalksFilterBar with improved timeout management to ensure smooth URL updates during rapid user interactions.
  • Documentation

    • Added troubleshooting guide documenting React's treatment of cleanup functions returned from event handlers, including best practices for debouncing and throttling.

…updates

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
devbcn Ready Ready Preview, Comment Mar 29, 2026 8:28am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

A React misconception regarding cleanup functions in event handlers is documented and corrected. The TalksFilterBar component's debouncing implementation was refactored to use a useRef-based approach with explicit clearTimeout instead of returning cleanup functions from the onChange handler.

Changes

Cohort / File(s) Summary
Documentation
.jules/bolt.md
Added troubleshooting entry explaining that React ignores cleanup functions returned from event handlers, documenting the use of useRef and clearTimeout for proper debouncing/throttling patterns.
Debouncing Implementation
components/layout/TalksFilterBar.tsx
Refactored debouncing logic to use debounceRef (via useRef) for managing timeout IDs, clearing prior timeouts before scheduling new ones to update filters after 300ms delay.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 The bounce was broken, cleanup ignored,
But useRef and clear brought back accord!
With timing fixed in FalterBar's way,
Debounced searches work just right today! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: correcting debounce logic in talks search to prevent excess URL updates, which is the core fix implemented in TalksFilterBar.tsx.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt-fix-debounce-15001196132622939092

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request fixes a debouncing bug in the TalksFilterBar component by utilizing useRef for timeout management and updates the project's learning documentation in .jules/bolt.md. Feedback suggests further improvements to make the component more robust, including adding a useEffect cleanup to prevent memory leaks on unmount, resolving a race condition when track filters are changed during a pending search, and extracting the debounce delay into a named constant.


import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useState, useTransition } from "react";
import { useCallback, useEffect, useRef, useState, useTransition } from "react";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The introduction of useRef for 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 with setTimeout will 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 useEffect hook. This ensures that if the component is unmounted, the timeout is cleared.

useEffect(() => {
  // Return a cleanup function from a useEffect with an empty dependency array
  return () => {
    if (debounceRef.current) {
      clearTimeout(debounceRef.current);
    }
  };
}, []); // This effect runs only once on mount and its cleanup on unmount.

Please add this effect to the component to make it more robust.

Comment on lines +71 to 73
debounceRef.current = setTimeout(() => {
updateFilters(selectedTrack, newQuery);
}, 300);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This implementation correctly debounces the search input. However, it introduces a potential race condition. The setTimeout callback captures the selectedTrack value at the time of the last keystroke. If the user changes the track filter after typing but before the 300ms debounce timeout fires, this callback will execute with the old, stale selectedTrack value, incorrectly reverting the track filter in the URL.

For example:

  1. User is on the "Java" track.
  2. User types in the search box. A timeout is scheduled to update the URL with track=Java.
  3. Before the timeout fires, the user clicks the "Cloud" track. The URL is immediately updated to track=Cloud.
  4. The original timeout fires and updates the URL back to track=Java, which is not what the user expects.

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 handleTrackClick function:

const handleTrackClick = (track: string) => {
  if (debounceRef.current) {
    clearTimeout(debounceRef.current);
  }
  // ... rest of the function
};

Since handleTrackClick is outside the changed lines in this PR, I'm pointing this out for your consideration to make the component more robust. You could also add a test case to cover this scenario.

const timeoutId = setTimeout(() => {
debounceRef.current = setTimeout(() => {
updateFilters(selectedTrack, newQuery);
}, 300);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The debounce delay 300 is a magic number. Consider extracting it into a named constant to improve readability and maintainability. This makes it easier to understand the purpose of the value and to change it consistently if needed.

For example:

const DEBOUNCE_DELAY_MS = 300;
// ...
debounceRef.current = setTimeout(() => {
  updateFilters(selectedTrack, newQuery);
}, DEBOUNCE_DELAY_MS);

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
components/layout/TalksFilterBar.tsx (1)

65-71: Drop “what” comments and keep only rationale comments.

Line 65 and Line 70 comments restate obvious behavior; please remove or rewrite them to capture only non-obvious intent.

As per coding guidelines, "Code must be self-documenting. Only explain why non-obvious decisions were made in comments. DO NOT add inline comments explaining what code does."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/layout/TalksFilterBar.tsx` around lines 65 - 71, In
TalksFilterBar, remove the inline "what" comments that restate obvious actions
around debounceRef.current and setTimeout (the comments on/around the
clearTimeout and debounce assignment) and replace them only with a short
rationale if needed (e.g., explain why we debounce URL updates for search or why
we clear previous timeout to avoid races), so keep comments that describe
non-obvious intent or trade-offs but delete comments that merely describe what
clearTimeout or setTimeout do.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/layout/TalksFilterBar.tsx`:
- Around line 14-15: A pending debounce timer in handleSearchChange can fire
after a user clicks a track and overwrite the newly-selected track; update
handleTrackClick to clear any pending timeout by checking and calling
clearTimeout(debounceRef.current) and setting debounceRef.current = null before
calling updateFilters, modify the setTimeout callback in handleSearchChange to
set debounceRef.current = null after it runs, and add a useEffect cleanup that
clears debounceRef.current (if present) on unmount to prevent timers firing
after the component is destroyed; refer to debounceRef, handleSearchChange,
handleTrackClick, updateFilters and the new useEffect cleanup for where to apply
these changes.

---

Nitpick comments:
In `@components/layout/TalksFilterBar.tsx`:
- Around line 65-71: In TalksFilterBar, remove the inline "what" comments that
restate obvious actions around debounceRef.current and setTimeout (the comments
on/around the clearTimeout and debounce assignment) and replace them only with a
short rationale if needed (e.g., explain why we debounce URL updates for search
or why we clear previous timeout to avoid races), so keep comments that describe
non-obvious intent or trade-offs but delete comments that merely describe what
clearTimeout or setTimeout do.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ff1b1978-33d4-4404-aac0-edfbe4c34d3b

📥 Commits

Reviewing files that changed from the base of the PR and between 76a14e4 and dfb6c96.

⛔ Files ignored due to path filters (1)
  • __tests__/snapshots/sections/home10/__snapshots__/Section1.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • .jules/bolt.md
  • components/layout/TalksFilterBar.tsx

Comment on lines +14 to 15
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 -C3

Repository: anyulled/devbcn-nextjs

Length of output: 2332


🏁 Script executed:

cat -n components/layout/TalksFilterBar.tsx | head -100

Repository: anyulled/devbcn-nextjs

Length of output: 3907


🏁 Script executed:

cat -n components/layout/TalksFilterBar.tsx | tail -80

Repository: anyulled/devbcn-nextjs

Length of output: 3394


🏁 Script executed:

cat -n __tests__/components/layout/TalksFilterBar.test.tsx

Repository: anyulled/devbcn-nextjs

Length of output: 2468


Clear pending debounce before non-debounced track clicks to prevent stale URL rewrites.

A pending timeout from handleSearchChange can fire after a track click and push stale filters using the captured selectedTrack from the timeout's closure. If a user types a search query and then clicks a track before the 300ms debounce expires, the pending timeout will fire with the original track value, causing two consecutive router updates and reverting the just-selected track.

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:

  • Clear debounceRef in handleTrackClick before calling updateFilters
  • Null debounceRef.current after the setTimeout callback executes
  • Add a useEffect cleanup function to clear any pending timeout on unmount
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
Verify each finding against the current code and only fix it if needed.

In `@components/layout/TalksFilterBar.tsx` around lines 14 - 15, A pending
debounce timer in handleSearchChange can fire after a user clicks a track and
overwrite the newly-selected track; update handleTrackClick to clear any pending
timeout by checking and calling clearTimeout(debounceRef.current) and setting
debounceRef.current = null before calling updateFilters, modify the setTimeout
callback in handleSearchChange to set debounceRef.current = null after it runs,
and add a useEffect cleanup that clears debounceRef.current (if present) on
unmount to prevent timers firing after the component is destroyed; refer to
debounceRef, handleSearchChange, handleTrackClick, updateFilters and the new
useEffect cleanup for where to apply these changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant