diff --git a/src/components/NoteCard/index.tsx b/src/components/NoteCard/index.tsx
index 9d4c70f..c63835d 100644
--- a/src/components/NoteCard/index.tsx
+++ b/src/components/NoteCard/index.tsx
@@ -3,11 +3,11 @@ import { isMentioningMutedUsers } from '@/lib/event'
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
import { useMuteList } from '@/providers/MuteListProvider'
import { Event, kinds } from 'nostr-tools'
-import { useMemo } from 'react'
+import { memo, useMemo } from 'react'
import MainNoteCard from './MainNoteCard'
import RepostNoteCard from './RepostNoteCard'
-export default function NoteCard({
+const NoteCard = memo(function NoteCard({
event,
className,
filterMutedNotes = true
@@ -35,7 +35,17 @@ export default function NoteCard({
)
}
return
-}
+}, (prevProps, nextProps) => {
+ // Custom comparison function for memo
+ return (
+ prevProps.event.id === nextProps.event.id &&
+ prevProps.event.created_at === nextProps.event.created_at &&
+ prevProps.className === nextProps.className &&
+ prevProps.filterMutedNotes === nextProps.filterMutedNotes
+ )
+})
+
+export default NoteCard
export function NoteCardLoadingSkeleton() {
return (
diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx
index d667145..ec3b152 100644
--- a/src/components/NoteList/index.tsx
+++ b/src/components/NoteList/index.tsx
@@ -79,6 +79,14 @@ const NoteList = forwardRef(
const supportTouch = useMemo(() => isTouchDevice(), [])
const bottomRef = useRef(null)
const topRef = useRef(null)
+
+ // Memoize subRequests serialization to avoid expensive JSON.stringify on every render
+ const subRequestsKey = useMemo(() => {
+ return JSON.stringify(subRequests.map(req => ({
+ urls: [...req.urls].sort(), // Create a copy before sorting to avoid mutation
+ filter: req.filter
+ })))
+ }, [subRequests])
const shouldHideEvent = useCallback(
(evt: Event) => {
@@ -252,7 +260,7 @@ const NoteList = forwardRef(
return () => {
promise.then((closer) => closer())
}
- }, [JSON.stringify(subRequests), refreshCount, showKinds])
+ }, [subRequestsKey, refreshCount, showKinds])
useEffect(() => {
const options = {
diff --git a/src/components/Profile/ProfileTimeline.tsx b/src/components/Profile/ProfileTimeline.tsx
index cb62c0c..4fc2c47 100644
--- a/src/components/Profile/ProfileTimeline.tsx
+++ b/src/components/Profile/ProfileTimeline.tsx
@@ -92,12 +92,19 @@ const ProfileTimeline = forwardRef<
if (!searchQuery.trim()) {
return eventsFilteredByKind
}
- const query = searchQuery.toLowerCase()
- return eventsFilteredByKind.filter(
- (event) =>
- event.content.toLowerCase().includes(query) ||
- event.tags.some((tag) => tag.length > 1 && tag[1]?.toLowerCase().includes(query))
- )
+ // Pre-compute lowercase query once
+ const query = searchQuery.toLowerCase().trim()
+ // Pre-compute lowercase content and tags for each event to avoid repeated conversions
+ return eventsFilteredByKind.filter((event) => {
+ const contentLower = event.content.toLowerCase()
+ if (contentLower.includes(query)) return true
+ // Only check tags if content doesn't match
+ return event.tags.some((tag) => {
+ if (tag.length <= 1) return false
+ const tagValue = tag[1]
+ return tagValue && tagValue.toLowerCase().includes(query)
+ })
+ })
}, [eventsFilteredByKind, searchQuery])
// Reset showCount when filters change