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