From 18ed116e0dca9ea7a84f2fe560ccdb7dd956c88c Mon Sep 17 00:00:00 2001 From: Sepehr Ramezani Date: Wed, 1 Apr 2026 22:27:29 +0200 Subject: [PATCH] perf: eliminate N+1 getNoteAllUsers calls and merge loadSettings+loadNotes to fix double-render cascade - Skip getNoteAllUsers server action for for own notes (majority of notes), Only fetch collaborators for shared notes. - Merge loadSettings and loadNotes into single useEffect to prevent double loadNotes trigger when showRecentNotes state changes. Co-Authored-By: Claude Opus 4.5 --- keep-notes/app/(main)/page.tsx | 23 ++++++++++------------- keep-notes/components/note-card.tsx | 23 ++++++++++++++++++----- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/keep-notes/app/(main)/page.tsx b/keep-notes/app/(main)/page.tsx index 6e3a93f..e37f1f9 100644 --- a/keep-notes/app/(main)/page.tsx +++ b/keep-notes/app/(main)/page.tsx @@ -152,23 +152,20 @@ export default function HomePage() { // Enable reminder notifications useReminderCheck(notes) - // Load user settings to check if recent notes should be shown + // Load settings + notes in a single effect to avoid cascade re-renders useEffect(() => { - const loadSettings = async () => { + const load = async () => { + // Load settings first + let showRecent = true try { const settings = await getAISettings() - // Default to true if setting is undefined or null - setShowRecentNotes(settings?.showRecentNotes !== false) + showRecent = settings?.showRecentNotes !== false } catch (error) { // Default to true on error - setShowRecentNotes(true) } - } - loadSettings() - }, [refreshKey]) + setShowRecentNotes(showRecent) - useEffect(() => { - const loadNotes = async () => { + // Then load notes setIsLoading(true) const search = searchParams.get('search')?.trim() || null const semanticMode = searchParams.get('semantic') === 'true' @@ -214,7 +211,7 @@ export default function HomePage() { setPinnedNotes(pinnedFilter) // Derive recent notes from already-fetched allNotes (no extra server call) - if (showRecentNotes) { + if (showRecent) { const sevenDaysAgo = new Date() sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7) sevenDaysAgo.setHours(0, 0, 0, 0) @@ -240,9 +237,9 @@ export default function HomePage() { setIsLoading(false) } - loadNotes() + load() // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searchParams, refreshKey, showRecentNotes]) // Intentionally omit 'labels' and 'semantic' to prevent reload when adding tags or from router.push + }, [searchParams, refreshKey]) // Intentionally omit 'labels' to prevent reload when adding tags // Get notebooks context to display header const { notebooks } = useNotebooks() const currentNotebook = notebooks.find((n: any) => n.id === searchParams.get('notebook')) diff --git a/keep-notes/components/note-card.tsx b/keep-notes/components/note-card.tsx index d6e0fa4..725ef39 100644 --- a/keep-notes/components/note-card.tsx +++ b/keep-notes/components/note-card.tsx @@ -184,17 +184,30 @@ export const NoteCard = memo(function NoteCard({ const isSharedNote = currentUserId && note.userId && currentUserId !== note.userId const isOwner = currentUserId && note.userId && currentUserId === note.userId - // Load collaborators when note changes + // Load collaborators only for shared notes (not owned by current user) useEffect(() => { + // Skip API call for notes owned by current user — no need to fetch collaborators + if (!isSharedNote) { + // For own notes, set owner to current user + if (currentUserId && session?.user) { + setOwner({ + id: currentUserId, + name: session.user.name, + email: session.user.email, + image: session.user.image, + }) + } + return + } + let isMounted = true - + const loadCollaborators = async () => { if (note.userId && isMounted) { try { const users = await getNoteAllUsers(note.id) if (isMounted) { setCollaborators(users) - // Owner is always first in the list if (users.length > 0) { setOwner(users[0]) } @@ -209,11 +222,11 @@ export const NoteCard = memo(function NoteCard({ } loadCollaborators() - + return () => { isMounted = false } - }, [note.id, note.userId]) + }, [note.id, note.userId, isSharedNote, currentUserId, session?.user]) const handleDelete = async () => { if (confirm(t('notes.confirmDelete'))) {