From fffd36268a216044523c3f5227c3d375608c36dc Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Sat, 14 Feb 2026 12:17:23 +0900
Subject: refactor(frontend): migrate state management to jotai and
 jotai-tanstack-query

Replace React Context + manual useEffect data fetching with jotai atoms
for state management and jotai-tanstack-query for server state caching.

- Add jotai, jotai-tanstack-query, @tanstack/query-core dependencies
- Create atoms for auth (primitive + action), feeds (suspense query),
  and articles (infinite query with cursor-based pagination)
- Wire up Provider, HydrateQueryClient, and StoreInitializer in main.tsx
- Migrate all components from useAuth() context to jotai atoms
- Replace manual fetch logic in FeedSidebar/FeedList with feedsAtom
- Replace usePaginatedArticles hook with articlesInfiniteAtom
- Add queryClient.invalidateQueries() after mutations for automatic
  cache refresh
- Add ErrorBoundary and LoadingSpinner components for Suspense support
- Remove callback prop chains (onFeedAdded, onFeedChanged, etc.)
  in favor of query invalidation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 frontend/src/components/FeedList.tsx | 63 +++---------------------------------
 1 file changed, 5 insertions(+), 58 deletions(-)

(limited to 'frontend/src/components/FeedList.tsx')

diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx
index a3ba124..364444f 100644
--- a/frontend/src/components/FeedList.tsx
+++ b/frontend/src/components/FeedList.tsx
@@ -1,58 +1,10 @@
-import { useCallback, useEffect, useState } from "react";
-import type { components } from "../api/generated";
-import { api } from "../services/api-client";
+import { useAtomValue } from "jotai";
+import { feedsAtom } from "../atoms";
 import { FeedItem } from "./FeedItem";
 
-type Feed = components["schemas"]["Feed"];
+export function FeedList() {
+	const { data: feeds } = useAtomValue(feedsAtom);
 
-interface Props {
-	onFeedUnsubscribed?: () => void;
-}
-
-export function FeedList({ onFeedUnsubscribed }: Props) {
-	const [feeds, setFeeds] = useState<Feed[]>([]);
-	const [fetching, setFetching] = useState(true);
-	const [error, setError] = useState<string | null>(null);
-
-	const fetchFeeds = useCallback(async () => {
-		setFetching(true);
-		const { data } = await api.GET("/api/feeds");
-		if (data) {
-			setFeeds(data);
-			setError(null);
-		} else {
-			setError("Failed to load feeds");
-		}
-		setFetching(false);
-	}, []);
-
-	useEffect(() => {
-		fetchFeeds();
-	}, [fetchFeeds]);
-
-	const handleFeedUnsubscribed = () => {
-		fetchFeeds();
-		onFeedUnsubscribed?.();
-	};
-
-	const handleFeedChanged = () => {
-		fetchFeeds();
-	};
-
-	if (fetching) {
-		return (
-			<div className="py-8 text-center">
-				<p className="text-sm text-stone-400">Loading feeds...</p>
-			</div>
-		);
-	}
-	if (error) {
-		return (
-			<div className="rounded-lg bg-red-50 p-4 text-sm text-red-600">
-				Error: {error}
-			</div>
-		);
-	}
 	if (feeds.length === 0) {
 		return (
 			<div className="py-8 text-center">
@@ -64,12 +16,7 @@ export function FeedList({ onFeedUnsubscribed }: Props) {
 	return (
 		<div className="space-y-3">
 			{feeds.map((feed) => (
-				<FeedItem
-					key={feed.id}
-					feed={feed}
-					onFeedUnsubscribed={handleFeedUnsubscribed}
-					onFeedChanged={handleFeedChanged}
-				/>
+				<FeedItem key={feed.id} feed={feed} />
 			))}
 		</div>
 	);
-- 
cgit v1.3-1-g0d28

