92 lines
2.4 KiB
TypeScript
92 lines
2.4 KiB
TypeScript
|
|
import { useCallback, useRef, useState } from "react";
|
||
|
|
import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store";
|
||
|
|
import type { DashboardStockSearchItem } from "@/features/dashboard/types/dashboard.types";
|
||
|
|
import { fetchStockSearch } from "@/features/dashboard/apis/kis-stock.api";
|
||
|
|
|
||
|
|
export function useStockSearch() {
|
||
|
|
const [keyword, setKeyword] = useState("삼성전자");
|
||
|
|
const [searchResults, setSearchResults] = useState<
|
||
|
|
DashboardStockSearchItem[]
|
||
|
|
>([]);
|
||
|
|
const [error, setError] = useState<string | null>(null);
|
||
|
|
const [isSearching, setIsSearching] = useState(false);
|
||
|
|
const requestIdRef = useRef(0);
|
||
|
|
const abortRef = useRef<AbortController | null>(null);
|
||
|
|
|
||
|
|
const loadSearch = useCallback(async (query: string) => {
|
||
|
|
const requestId = ++requestIdRef.current;
|
||
|
|
const controller = new AbortController();
|
||
|
|
abortRef.current?.abort();
|
||
|
|
abortRef.current = controller;
|
||
|
|
|
||
|
|
setIsSearching(true);
|
||
|
|
setError(null);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const data = await fetchStockSearch(query, controller.signal);
|
||
|
|
if (requestId === requestIdRef.current) {
|
||
|
|
setSearchResults(data.items);
|
||
|
|
}
|
||
|
|
return data.items;
|
||
|
|
} catch (err) {
|
||
|
|
if (controller.signal.aborted) {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
if (requestId === requestIdRef.current) {
|
||
|
|
setError(
|
||
|
|
err instanceof Error
|
||
|
|
? err.message
|
||
|
|
: "종목 검색 중 오류가 발생했습니다.",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return [];
|
||
|
|
} finally {
|
||
|
|
if (requestId === requestIdRef.current) {
|
||
|
|
setIsSearching(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const search = useCallback(
|
||
|
|
(query: string, credentials: KisRuntimeCredentials | null) => {
|
||
|
|
if (!credentials) {
|
||
|
|
setError("API 키 검증이 필요합니다.");
|
||
|
|
setSearchResults([]);
|
||
|
|
setIsSearching(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const trimmed = query.trim();
|
||
|
|
if (!trimmed) {
|
||
|
|
abortRef.current?.abort();
|
||
|
|
setSearchResults([]);
|
||
|
|
setError(null);
|
||
|
|
setIsSearching(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
void loadSearch(trimmed);
|
||
|
|
},
|
||
|
|
[loadSearch],
|
||
|
|
);
|
||
|
|
|
||
|
|
const clearSearch = useCallback(() => {
|
||
|
|
abortRef.current?.abort();
|
||
|
|
setSearchResults([]);
|
||
|
|
setError(null);
|
||
|
|
setIsSearching(false);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return {
|
||
|
|
keyword,
|
||
|
|
setKeyword,
|
||
|
|
searchResults,
|
||
|
|
setSearchResults,
|
||
|
|
error,
|
||
|
|
setError,
|
||
|
|
isSearching,
|
||
|
|
search,
|
||
|
|
clearSearch,
|
||
|
|
};
|
||
|
|
}
|