diff --git a/web/src/components/MemoResourceListView.tsx b/web/src/components/MemoResourceListView.tsx
index 538f0ac3..8ffb2772 100644
--- a/web/src/components/MemoResourceListView.tsx
+++ b/web/src/components/MemoResourceListView.tsx
@@ -1,4 +1,4 @@
-import classNames from "classnames";
+import { memo } from "react";
 import { absolutifyLink } from "@/helpers/utils";
 import { Resource } from "@/types/proto/api/v2/resource_service";
 import { getResourceType, getResourceUrl } from "@/utils/resource";
@@ -6,106 +6,102 @@ import MemoResource from "./MemoResource";
 import showPreviewImageDialog from "./PreviewImageDialog";
 import SquareDiv from "./kit/SquareDiv";
 
-interface Props {
-  resourceList: Resource[];
-  className?: string;
-}
+const MemoResourceListView = ({ resourceList = [] }: { resourceList: Resource[] }) => {
+  const mediaResources: Resource[] = [];
+  const otherResources: Resource[] = [];
 
-const getDefaultProps = (): Props => {
-  return {
-    className: "",
-    resourceList: [],
-  };
-};
+  resourceList.forEach((resource) => {
+    const type = getResourceType(resource);
+    if (type === "image/*" || type === "video/*") {
+      mediaResources.push(resource);
+      return;
+    }
 
-const MemoResourceListView: React.FC<Props> = (props: Props) => {
-  const { className, resourceList } = {
-    ...getDefaultProps(),
-    ...props,
-  };
-  const imageResourceList = resourceList.filter((resource) => getResourceType(resource).startsWith("image"));
-  const videoResourceList = resourceList.filter((resource) => resource.type.startsWith("video"));
-  const otherResourceList = resourceList.filter(
-    (resource) => !imageResourceList.includes(resource) && !videoResourceList.includes(resource)
-  );
-
-  const imgUrls = imageResourceList.map((resource) => {
-    return getResourceUrl(resource);
+    otherResources.push(resource);
   });
 
   const handleImageClick = (imgUrl: string) => {
+    const imgUrls = mediaResources
+      .filter((resource) => getResourceType(resource) === "image/*")
+      .map((resource) => getResourceUrl(resource));
     const index = imgUrls.findIndex((url) => url === imgUrl);
     showPreviewImageDialog(imgUrls, index);
   };
 
+  const MediaCard = ({ resource, thumbnail }: { resource: Resource; thumbnail?: boolean }) => {
+    const type = getResourceType(resource);
+    const url = getResourceUrl(resource);
+    if (type === "image/*") {
+      return (
+        <img
+          className="cursor-pointer min-h-full w-auto object-cover"
+          src={resource.externalLink ? url : `${url}${thumbnail ? "?thumbnail=1" : ""}`}
+          onClick={() => handleImageClick(url)}
+          decoding="async"
+        />
+      );
+    }
+
+    if (type === "video/*") {
+      return (
+        <video
+          className="cursor-pointer w-full h-full object-contain bg-zinc-100 dark:bg-zinc-800"
+          preload="metadata"
+          crossOrigin="anonymous"
+          src={absolutifyLink(url)}
+          controls
+        />
+      );
+    }
+
+    return <></>;
+  };
+
+  const MediaList = ({ resources = [] }: { resources: Resource[] }) => {
+    if (resources.length === 0) return <></>;
+
+    if (resources.length === 1) {
+      return (
+        <div className="mt-2 max-w-full max-h-72 flex justify-center items-center border dark:border-zinc-800 rounded overflow-hidden hide-scrollbar hover:shadow-md">
+          <MediaCard resource={mediaResources[0]} />
+        </div>
+      );
+    }
+
+    const cards = resources.map((resource) => (
+      <SquareDiv
+        key={resource.id}
+        className="flex justify-center items-center border dark:border-zinc-900 rounded overflow-hidden hide-scrollbar hover:shadow-md"
+      >
+        <MediaCard resource={resource} thumbnail />
+      </SquareDiv>
+    ));
+
+    if (resources.length === 2 || resources.length === 4) {
+      return <div className="w-full mt-2 grid gap-2 grid-cols-2">{cards}</div>;
+    }
+
+    return <div className="w-full mt-2 grid gap-2 grid-cols-2 sm:grid-cols-3">{cards}</div>;
+  };
+
+  const OtherList = ({ resources = [] }: { resources: Resource[] }) => {
+    if (resources.length === 0) return <></>;
+
+    return (
+      <div className="w-full flex flex-row justify-start flex-wrap mt-2">
+        {otherResources.map((resource) => (
+          <MemoResource key={resource.id} className="my-1 mr-2" resource={resource} />
+        ))}
+      </div>
+    );
+  };
+
   return (
     <>
-      {imageResourceList.length > 0 &&
-        (imageResourceList.length === 1 ? (
-          <div className="mt-2 max-w-full max-h-72 flex justify-center items-center border dark:border-zinc-800 rounded overflow-hidden hide-scrollbar hover:shadow-md">
-            <img
-              className="cursor-pointer min-h-full w-auto object-cover"
-              src={getResourceUrl(imageResourceList[0])}
-              onClick={() => handleImageClick(getResourceUrl(imageResourceList[0]))}
-              decoding="async"
-            />
-          </div>
-        ) : (
-          <div
-            className={classNames(
-              "w-full mt-2 grid gap-2 grid-cols-2",
-              imageResourceList.length === 4 ? "sm:grid-cols-2" : "sm:grid-cols-3"
-            )}
-          >
-            {imageResourceList.map((resource) => {
-              const url = getResourceUrl(resource);
-              return (
-                <SquareDiv
-                  key={resource.id}
-                  className="flex justify-center items-center border dark:border-zinc-900 rounded overflow-hidden hide-scrollbar hover:shadow-md"
-                >
-                  <img
-                    className="cursor-pointer min-h-full w-auto object-cover"
-                    src={resource.externalLink ? url : url + "?thumbnail=1"}
-                    onClick={() => handleImageClick(url)}
-                    decoding="async"
-                  />
-                </SquareDiv>
-              );
-            })}
-          </div>
-        ))}
-
-      <div className={`w-full flex flex-col justify-start items-start ${className || ""}`}>
-        {videoResourceList.length > 0 && (
-          <div className="w-full grid grid-cols-2 sm:grid-cols-3 gap-2 mt-2">
-            {videoResourceList.map((resource) => {
-              const url = getResourceUrl(resource);
-              return (
-                <SquareDiv key={resource.id} className="shadow rounded overflow-hidden hide-scrollbar">
-                  <video
-                    className="cursor-pointer w-full h-full object-contain bg-zinc-100 dark:bg-zinc-800"
-                    preload="metadata"
-                    crossOrigin="anonymous"
-                    src={absolutifyLink(url)}
-                    controls
-                  ></video>
-                </SquareDiv>
-              );
-            })}
-          </div>
-        )}
-      </div>
-
-      {otherResourceList.length > 0 && (
-        <div className="w-full flex flex-row justify-start flex-wrap mt-2">
-          {otherResourceList.map((resource) => {
-            return <MemoResource key={resource.id} className="my-1 mr-2" resource={resource} />;
-          })}
-        </div>
-      )}
+      <MediaList resources={mediaResources} />
+      <OtherList resources={otherResources} />
     </>
   );
 };
 
-export default MemoResourceListView;
+export default memo(MemoResourceListView);
diff --git a/web/src/components/ShareMemoDialog.tsx b/web/src/components/ShareMemoDialog.tsx
index 6ab547a7..593b64d2 100644
--- a/web/src/components/ShareMemoDialog.tsx
+++ b/web/src/components/ShareMemoDialog.tsx
@@ -113,7 +113,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
             <span className="w-full px-6 pt-5 pb-2 text-sm text-gray-500">{memo.displayTsStr}</span>
             <div className="w-full px-6 text-base pb-4">
               <MemoContent content={memo.content} />
-              <MemoResourceListView className="!grid-cols-2" resourceList={memo.resourceList} />
+              <MemoResourceListView resourceList={memo.resourceList} />
             </div>
             <div className="flex flex-row justify-between items-center w-full bg-gray-100 dark:bg-zinc-700 py-4 px-6">
               <div className="flex flex-row justify-start items-center">