From 987f78ac25af119796c6f3830a61aafcabeb2dc1 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Fri, 22 Aug 2025 23:17:09 +0900
Subject: fix(frontend): wrong optimistic update

---
 frontend/src/components/ArticleItem.tsx | 38 +++++++++++----------------------
 1 file changed, 13 insertions(+), 25 deletions(-)

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

diff --git a/frontend/src/components/ArticleItem.tsx b/frontend/src/components/ArticleItem.tsx
index faa86fe..4942518 100644
--- a/frontend/src/components/ArticleItem.tsx
+++ b/frontend/src/components/ArticleItem.tsx
@@ -1,6 +1,5 @@
 import { faCheck, faCircle } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { useOptimistic } from "react";
 import { useMutation } from "urql";
 import type {
 	GetReadArticlesQuery,
@@ -18,27 +17,19 @@ type Article = NonNullable<
 
 interface Props {
 	article: Article;
+	onReadChange?: (articleId: string, isRead: boolean) => void;
 }
 
-export function ArticleItem({ article }: Props) {
+export function ArticleItem({ article, onReadChange }: Props) {
 	const [, markArticleRead] = useMutation(MarkArticleReadDocument);
 	const [, markArticleUnread] = useMutation(MarkArticleUnreadDocument);
 
-	const [optimisticArticle, setOptimisticArticle] = useOptimistic(
-		article,
-		(currentArticle, newReadState: boolean) => ({
-			...currentArticle,
-			isRead: newReadState,
-		}),
-	);
-
 	const handleToggleRead = async (
 		articleId: string,
 		isCurrentlyRead: boolean,
 	) => {
 		const newReadState = !isCurrentlyRead;
-
-		setOptimisticArticle(newReadState);
+		onReadChange?.(articleId, newReadState);
 
 		if (isCurrentlyRead) {
 			await markArticleUnread({ id: articleId });
@@ -50,9 +41,8 @@ export function ArticleItem({ article }: Props) {
 	const handleArticleClick = async (article: Article) => {
 		// Open article in new tab and mark as read if it's unread
 		window.open(article.url, "_blank", "noreferrer");
-		if (!optimisticArticle.isRead) {
-			setOptimisticArticle(true);
-
+		if (!article.isRead) {
+			onReadChange?.(article.id, true);
 			await markArticleRead({ id: article.id });
 		}
 	};
@@ -60,38 +50,36 @@ export function ArticleItem({ article }: Props) {
 	return (
 		<div
 			className={`group flex items-center gap-3 rounded-lg border p-3 hover:bg-gray-50 ${
-				optimisticArticle.isRead
+				article.isRead
 					? "border-gray-200 bg-white"
 					: "border-blue-200 bg-blue-50"
 			}`}
 		>
 			<button
 				type="button"
-				onClick={() => handleToggleRead(article.id, optimisticArticle.isRead)}
+				onClick={() => handleToggleRead(article.id, article.isRead)}
 				className={`flex-shrink-0 rounded p-1 transition-colors ${
-					optimisticArticle.isRead
+					article.isRead
 						? "text-gray-400 hover:text-gray-600"
 						: "text-blue-600 hover:text-blue-700"
 				}`}
-				title={optimisticArticle.isRead ? "Mark as unread" : "Mark as read"}
+				title={article.isRead ? "Mark as unread" : "Mark as read"}
 			>
 				<FontAwesomeIcon
-					icon={optimisticArticle.isRead ? faCheck : faCircle}
+					icon={article.isRead ? faCheck : faCircle}
 					className="w-4 h-4"
 				/>
 			</button>
 			<div className="flex-1 min-w-0">
 				<button
 					type="button"
-					onClick={() => handleArticleClick(optimisticArticle)}
+					onClick={() => handleArticleClick(article)}
 					className={`text-left w-full group-hover:text-blue-600 transition-colors ${
-						optimisticArticle.isRead
-							? "text-gray-700"
-							: "text-gray-900 font-medium"
+						article.isRead ? "text-gray-700" : "text-gray-900 font-medium"
 					}`}
 				>
 					<div className="flex items-center gap-2 break-words">
-						{optimisticArticle.title}
+						{article.title}
 					</div>
 				</button>
 			</div>
-- 
cgit v1.2.3-70-g09d2

