diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-08-22 23:17:09 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-08-22 23:17:09 +0900 |
| commit | 987f78ac25af119796c6f3830a61aafcabeb2dc1 (patch) | |
| tree | a96e510e28c389a14f700aceb9ab71b3edbb8b71 | |
| parent | 0f0a08196a045d49cf98bb2b841e6475cc0c94a6 (diff) | |
| download | feedaka-987f78ac25af119796c6f3830a61aafcabeb2dc1.tar.gz feedaka-987f78ac25af119796c6f3830a61aafcabeb2dc1.tar.zst feedaka-987f78ac25af119796c6f3830a61aafcabeb2dc1.zip | |
fix(frontend): wrong optimistic update
| -rw-r--r-- | frontend/src/components/ArticleItem.tsx | 38 | ||||
| -rw-r--r-- | frontend/src/components/ArticleList.tsx | 28 | ||||
| -rw-r--r-- | frontend/src/pages/ReadArticles.tsx | 4 | ||||
| -rw-r--r-- | frontend/src/pages/UnreadArticles.tsx | 4 |
4 files changed, 43 insertions, 31 deletions
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> diff --git a/frontend/src/components/ArticleList.tsx b/frontend/src/components/ArticleList.tsx index 2ea94f0..574f529 100644 --- a/frontend/src/components/ArticleList.tsx +++ b/frontend/src/components/ArticleList.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import type { GetReadArticlesQuery, GetUnreadArticlesQuery, @@ -9,17 +10,32 @@ interface Props { | GetUnreadArticlesQuery["unreadArticles"] | GetReadArticlesQuery["readArticles"] >; + isReadView?: boolean; } -export function ArticleList({ articles }: Props) { - if (articles.length === 0) { +export function ArticleList({ articles, isReadView }: Props) { + const [hiddenArticleIds, setHiddenArticleIds] = useState<Set<string>>( + new Set(), + ); + + const handleArticleReadChange = (articleId: string, isRead: boolean) => { + if (isReadView !== isRead) { + setHiddenArticleIds((prev) => new Set(prev).add(articleId)); + } + }; + + const visibleArticles = articles.filter( + (article) => !hiddenArticleIds.has(article.id), + ); + + if (visibleArticles.length === 0) { return ( <div className="p-4 text-center text-gray-500">No articles found.</div> ); } // Group articles by feed - const articlesByFeed = articles.reduce( + const articlesByFeed = visibleArticles.reduce( (acc, article) => { const feedId = article.feed.id; if (!acc[feedId]) { @@ -50,7 +66,11 @@ export function ArticleList({ articles }: Props) { </h3> <div className="space-y-1"> {feedArticles.map((article) => ( - <ArticleItem key={article.id} article={article} /> + <ArticleItem + key={article.id} + article={article} + onReadChange={handleArticleReadChange} + /> ))} </div> </div> diff --git a/frontend/src/pages/ReadArticles.tsx b/frontend/src/pages/ReadArticles.tsx index 44ae834..74c1f5c 100644 --- a/frontend/src/pages/ReadArticles.tsx +++ b/frontend/src/pages/ReadArticles.tsx @@ -26,7 +26,9 @@ export function ReadArticles() { </p> )} </div> - {data?.readArticles && <ArticleList articles={data.readArticles} />} + {data?.readArticles && ( + <ArticleList articles={data.readArticles} isReadView={true} /> + )} </div> ); } diff --git a/frontend/src/pages/UnreadArticles.tsx b/frontend/src/pages/UnreadArticles.tsx index f82d4dc..45ae0f9 100644 --- a/frontend/src/pages/UnreadArticles.tsx +++ b/frontend/src/pages/UnreadArticles.tsx @@ -26,7 +26,9 @@ export function UnreadArticles() { </p> )} </div> - {data?.unreadArticles && <ArticleList articles={data.unreadArticles} />} + {data?.unreadArticles && ( + <ArticleList articles={data.unreadArticles} isReadView={false} /> + )} </div> ); } |
