fix: debounce ticker search on Trends page to preserve input focus
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/finalize Pipeline was successful
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled

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)}