Browse Source

refactor(spec): memoize useMergedInAppNotifications handlers and wrap NewsItem with memo

Ryotaro Nagahara 3 weeks ago
parent
commit
f7870e4ed1

+ 26 - 18
apps/app/src/client/components/Sidebar/InAppNotification/hooks/useMergedInAppNotifications.ts

@@ -1,4 +1,4 @@
-import { useMemo } from 'react';
+import { useCallback, useMemo } from 'react';
 import type { SWRInfiniteResponse } from 'swr/infinite';
 
 import {
@@ -156,27 +156,35 @@ export const useMergedInAppNotifications = (
     );
   }, [allNewsItems, allNotificationItems]);
 
-  const handleReadMutate = () => {
-    newsResponse.mutate();
+  // SWR's mutate is stable per cache key — destructure once and depend on it
+  // rather than the whole response object (which may carry unstable identity).
+  const { mutate: mutateNews } = newsResponse;
+  const { mutate: mutateNotifications } = notificationResponse;
+
+  const handleReadMutate = useCallback(() => {
+    mutateNews();
     mutateNewsUnreadCount();
-  };
+  }, [mutateNews, mutateNewsUnreadCount]);
 
   // SWR-idiomatic optimistic update: rewrite the per-page cache in place and
   // suppress revalidation so the dot stays removed across unmount/remount.
-  const handleNotificationRead = (notificationId: string) => {
-    notificationResponse.mutate(
-      (pages) =>
-        pages?.map((page) => ({
-          ...page,
-          docs: page.docs.map((doc) =>
-            doc._id.toString() === notificationId
-              ? { ...doc, status: InAppNotificationStatuses.STATUS_OPENED }
-              : doc,
-          ),
-        })),
-      { revalidate: false },
-    );
-  };
+  const handleNotificationRead = useCallback(
+    (notificationId: string) => {
+      mutateNotifications(
+        (pages) =>
+          pages?.map((page) => ({
+            ...page,
+            docs: page.docs.map((doc) =>
+              doc._id.toString() === notificationId
+                ? { ...doc, status: InAppNotificationStatuses.STATUS_OPENED }
+                : doc,
+            ),
+          })),
+        { revalidate: false },
+      );
+    },
+    [mutateNotifications],
+  );
 
   return {
     newsResponse,

+ 4 - 1
apps/app/src/features/news/client/components/NewsItem.tsx

@@ -1,4 +1,5 @@
 import type { FC } from 'react';
+import { memo } from 'react';
 import { format } from 'date-fns';
 import { useTranslation } from 'next-i18next';
 
@@ -30,7 +31,7 @@ type Props = {
   onReadMutate: () => void;
 };
 
-export const NewsItem: FC<Props> = ({ item, onReadMutate }) => {
+const NewsItemInner: FC<Props> = ({ item, onReadMutate }) => {
   const { i18n } = useTranslation();
   const locale = i18n.language;
   const title = resolveTitle(item.title, locale);
@@ -78,3 +79,5 @@ export const NewsItem: FC<Props> = ({ item, onReadMutate }) => {
     </button>
   );
 };
+
+export const NewsItem = memo(NewsItemInner);