From a3f2894c0cd120eed85bf19c0b450b30c3fb0e04 Mon Sep 17 00:00:00 2001 From: Anshul Jain <167362756+anshul23102@users.noreply.github.com> Date: Thu, 2 Jul 2026 03:07:23 +0530 Subject: [PATCH] fix(perf): debounce search input to prevent excessive filter re-renders The org search input and mentor search input triggered a full filter/render operation on every keystroke, causing 15+ synchronous filter operations when typing a typical query. This adds a debounce(fn, delay) utility and wraps both the org search and mentor search filter calls with a 300ms debounce, so filtering only runs once the user pauses typing. Fixes #1970 Signed-off-by: Anshul Jain --- src/js/app.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index d1c9415db2..f9825f4099 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -49,6 +49,22 @@ const CATEGORY_META = { other: { className: 'bg-zinc-100 text-zinc-600', label: 'Other' }, }; +// ══════════════════════════════════════════════ +// UTILITY FUNCTIONS +// ══════════════════════════════════════════════ +/** + * Returns a debounced version of fn that delays invoking it until after + * `delay` ms have elapsed since the last call. Prevents excessive filter + * re-renders on every keystroke in search inputs. + */ +function debounce(fn, delay) { + let timer; + return function (...args) { + clearTimeout(timer); + timer = setTimeout(() => fn.apply(this, args), delay); + }; +} + const LANGUAGE_ALIASES = { 'python': ['python'], 'javascript': ['javascript', 'js'], @@ -2463,11 +2479,13 @@ document.addEventListener('DOMContentLoaded', () => { renderGoodFirstIssues(); // Wire up filter event listeners - document.getElementById('searchInput')?.addEventListener('input', applyFilters); + const debouncedApplyFilters = debounce(applyFilters, 300); + document.getElementById('searchInput')?.addEventListener('input', debouncedApplyFilters); document.getElementById('categoryFilter')?.addEventListener('change', applyFilters); document.getElementById('complexityFilter')?.addEventListener('change', applyFilters); document.getElementById('sortSelect')?.addEventListener('change', applyFilters); - document.getElementById('mentorSearchInput')?.addEventListener('input', renderMentorFinder); + const debouncedRenderMentorFinder = debounce(renderMentorFinder, 300); + document.getElementById('mentorSearchInput')?.addEventListener('input', debouncedRenderMentorFinder); document.getElementById('mentorChannelFilter')?.addEventListener('change', renderMentorFinder); document.getElementById('matchAllLanguagesToggle')?.addEventListener('change', (e) => { matchAllLanguages = e.target.checked; @@ -2518,7 +2536,7 @@ document.addEventListener('DOMContentLoaded', () => { searchInput.value = e.target.value; const orgsSec = document.getElementById('orgs'); if (orgsSec) orgsSec.scrollIntoView({ behavior: 'smooth' }); - applyFilters(); + debouncedApplyFilters(); } }); }