Releases: Stock-XAI/frontend
Bi-Weekly-Snapshot - 04/14 (frontend)
Bi-Weekly-Snapshot - 04/14 (frontend)
Index
- Code Structure
- Main Code Explanation
Code Structure

The project is structured as follows:
• api: Contains code for API-related communication.
• assets: Stores static image files and SVGs.
• components: Holds reusable common components.
• constants: Used for managing constant values.
• hooks: Contains custom hook logic.
• pages: Manages individual page components.
• styles: Defines global styles using styled-components.
• types: Stores type definitions.
• utils: Includes commonly used utility functions.
Main Code Explanation
App.tsx
function App() {
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<GlobalStyle />
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/detail" element={<Detail />} />
</Routes>
</BrowserRouter>
</ThemeProvider>
</QueryClientProvider>
);
}
export default App;In the App.tsx file, the necessary basic setup was completed, including configurations for React Query, Styled-Components, and route paths. The component is the first one rendered when entering the service, while the component is rendered when a stock is searched.
api/instance.ts
import axios from "axios";
const instance = axios.create({
baseURL: import.meta.env.VITE_API_URL,
headers: {
"Content-Type": "application/json",
},
});
export default instance;This section creates a common Axios instance for backend communication. The baseURL is loaded from the .env file, where the backend URL (e.g., localhost:8000) is stored and used.
api/apis.ts
export async function getStockInfo(params: StockInfoParams) {
const res = await instance.get<StockInfoResponse>("stock-info", {
params: params,
});
return res.data;
}
export async function getSearch(params: SearchParams) {
const res = await instance.get<Stock[]>("search", {
params: params,
});
return res.data;
}
export interface StockInfoParams {
ticker: string;
horizon?: number;
includeNews?: boolean;
includeXAI?: boolean;
}
export interface StockInfoResponse {
success: boolean;
message: string;
data: StockInfo;
}
export interface SearchParams {
keyword?: string;
}The getStockInfo function retrieves prediction results related to a stock, while the getSearch function fetches stock information based on a search keyword. Type definitions for the required parameters and responses were also included.
hooks/useStockQuery.ts
export const stockKeys = {
all: ["stock"] as const,
search: (query: string) => [...stockKeys.all, "search", query] as const,
stockInfo: (queries: StockInfoParams) =>
[...stockKeys.all, "stockInfo", queries] as const,
};
export function useStockSearch(params: SearchParams, disabled: boolean) {
return useQuery({
queryKey: stockKeys.search(params.keyword || "all"),
queryFn: () => getSearch(params),
enabled: !disabled, //요청 보낼지 여부
staleTime: 1000 * 60 * 60 * 24, //하루동안 캐시 유지
retry: 2, //요청 실패시 재요청 횟수
});
}
export function useStockInfo(params: StockInfoParams) {
return useQuery({
queryKey: stockKeys.stockInfo(params),
queryFn: () => getStockInfo(params),
enabled: true, //요청 보낼지 여부, 추후 변수로 관리 가능
staleTime: 1000 * 60 * 60 * 1, //한 시간동안 캐시 유지
retry: 2,
});
}A custom hook was created using React Query’s useQuery function. A unique queryKey was set for caching, enabled determines whether the request should be sent when the function is triggered, staleTime defines how long the cached data remains fresh, and retry specifies the number of retry attempts in case of a request failure.
pages/home/index.ts
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute("data-id");
if (id) setActive(id);
}
});
},
{ threshold: 0.6 }
);
sectionRefs.current.forEach((el) => el && observer.observe(el));
return () => observer.disconnect();
}, []);To implement the functionality where the active table of contents changes based on scroll position on the landing page, an observer was used.
const { data: searchData } = useStockSearch(
{ keyword },
keyword.length == 0
);
useEffect(() => {
if (searchData) {
setSearchResult(searchData);
}
}, [searchData]);A custom hook is used so that it automatically triggers whenever the user’s input keyword changes. However, if the length of the keyword is 0, the disabled variable is set to true, preventing the hook from being called. Whenever searchData changes, the useEffect hook is used to update searchResult accordingly.
<Input
placeholder="Enter the Stock Ticker or Name."
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
{keyword.length > 0 && searchResult.length > 0 && (
<SearchResultContainer>
<ul>
{searchResult.map((item) => (
<StockInfoItem
key={item.ticker}
onClick={() => {
handleStockPrediction(item);
}}
>
<strong>{item.ticker}</strong> - {item.name}
</StockInfoItem>
))}
</ul>
</SearchResultContainer>
)}
{keyword.length > 0 && searchResult.length === 0 && (
<SearchResultContainer>
<h3>No results found.</h3>
</SearchResultContainer>
)}The input value is stored in the keyword variable whenever it changes through the Input component. The search result UI is displayed only when the keyword length is at least 1 and there is at least one search result. If a keyword exists but no results are found, a “no results” UI is shown instead.

