fix: debounce ticker search on Trends page to preserve input focus

The TickerFilter triggered a query on every keystroke, causing re-renders
that stole focus from the input. Now uses a local input state with a
300ms debounce before updating the query, keeping focus on the text box
while typing.
This commit is contained in:
Celes Renata
2026-04-29 16:54:19 +00:00
parent 6880f11c26
commit 24c753f6e6
+21 -4
View File
@@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState, useRef, useEffect } from 'react';
import { useNavigate, Link } from '@tanstack/react-router'; import { useNavigate, Link } from '@tanstack/react-router';
import { useTrends, useDocument } from '../api/hooks'; import { useTrends, useDocument } from '../api/hooks';
import { TrendArrow, ConfidenceBar, LoadingSpinner, TickerFilter, Card } from '../components/ui'; import { TrendArrow, ConfidenceBar, LoadingSpinner, Card } from '../components/ui';
import type { TrendSummary } from '../api/hooks'; import type { TrendSummary } from '../api/hooks';
const WINDOWS = ['intraday', '1d', '7d', '30d', '90d']; const WINDOWS = ['intraday', '1d', '7d', '30d', '90d'];
@@ -9,8 +9,17 @@ const WINDOWS = ['intraday', '1d', '7d', '30d', '90d'];
export function TrendsPage() { export function TrendsPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const [ticker, setTicker] = useState(''); const [ticker, setTicker] = useState('');
const [debouncedTicker, setDebouncedTicker] = useState('');
const [window, setWindow] = useState<string | undefined>(undefined); const [window, setWindow] = useState<string | undefined>(undefined);
const { data, isLoading } = useTrends({ ticker: ticker || undefined, window, limit: 100 }); const inputRef = useRef<HTMLInputElement>(null);
// Debounce ticker search — only query after 300ms of no typing
useEffect(() => {
const timer = setTimeout(() => setDebouncedTicker(ticker), 300);
return () => clearTimeout(timer);
}, [ticker]);
const { data, isLoading } = useTrends({ ticker: debouncedTicker || undefined, window, limit: 100 });
if (isLoading) return <LoadingSpinner />; if (isLoading) return <LoadingSpinner />;
@@ -19,7 +28,15 @@ export function TrendsPage() {
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<h1 className="text-xl font-semibold text-gray-100">Trends</h1> <h1 className="text-xl font-semibold text-gray-100">Trends</h1>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<TickerFilter value={ticker} onChange={setTicker} /> <input
ref={inputRef}
type="text"
placeholder="Ticker…"
value={ticker}
onChange={(e) => setTicker(e.target.value.toUpperCase())}
className="w-24 rounded-md border border-surface-700 bg-surface-900 px-2 py-1 text-xs text-gray-200 placeholder-gray-500 focus:border-brand-500 focus:outline-none"
aria-label="Filter by ticker"
/>
<div className="inline-flex rounded-md border border-surface-700" role="group" aria-label="Window selector"> <div className="inline-flex rounded-md border border-surface-700" role="group" aria-label="Window selector">
<button <button
onClick={() => setWindow(undefined)} onClick={() => setWindow(undefined)}