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 <noreply@anthropic.com>
This commit is contained in:
@@ -152,23 +152,20 @@ export default function HomePage() {
|
|||||||
// Enable reminder notifications
|
// Enable reminder notifications
|
||||||
useReminderCheck(notes)
|
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(() => {
|
useEffect(() => {
|
||||||
const loadSettings = async () => {
|
const load = async () => {
|
||||||
|
// Load settings first
|
||||||
|
let showRecent = true
|
||||||
try {
|
try {
|
||||||
const settings = await getAISettings()
|
const settings = await getAISettings()
|
||||||
// Default to true if setting is undefined or null
|
showRecent = settings?.showRecentNotes !== false
|
||||||
setShowRecentNotes(settings?.showRecentNotes !== false)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Default to true on error
|
// Default to true on error
|
||||||
setShowRecentNotes(true)
|
|
||||||
}
|
}
|
||||||
}
|
setShowRecentNotes(showRecent)
|
||||||
loadSettings()
|
|
||||||
}, [refreshKey])
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Then load notes
|
||||||
const loadNotes = async () => {
|
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const search = searchParams.get('search')?.trim() || null
|
const search = searchParams.get('search')?.trim() || null
|
||||||
const semanticMode = searchParams.get('semantic') === 'true'
|
const semanticMode = searchParams.get('semantic') === 'true'
|
||||||
@@ -214,7 +211,7 @@ export default function HomePage() {
|
|||||||
setPinnedNotes(pinnedFilter)
|
setPinnedNotes(pinnedFilter)
|
||||||
|
|
||||||
// Derive recent notes from already-fetched allNotes (no extra server call)
|
// Derive recent notes from already-fetched allNotes (no extra server call)
|
||||||
if (showRecentNotes) {
|
if (showRecent) {
|
||||||
const sevenDaysAgo = new Date()
|
const sevenDaysAgo = new Date()
|
||||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
|
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
|
||||||
sevenDaysAgo.setHours(0, 0, 0, 0)
|
sevenDaysAgo.setHours(0, 0, 0, 0)
|
||||||
@@ -240,9 +237,9 @@ export default function HomePage() {
|
|||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNotes()
|
load()
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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
|
// Get notebooks context to display header
|
||||||
const { notebooks } = useNotebooks()
|
const { notebooks } = useNotebooks()
|
||||||
const currentNotebook = notebooks.find((n: any) => n.id === searchParams.get('notebook'))
|
const currentNotebook = notebooks.find((n: any) => n.id === searchParams.get('notebook'))
|
||||||
|
|||||||
@@ -184,17 +184,30 @@ export const NoteCard = memo(function NoteCard({
|
|||||||
const isSharedNote = currentUserId && note.userId && currentUserId !== note.userId
|
const isSharedNote = currentUserId && note.userId && currentUserId !== note.userId
|
||||||
const isOwner = 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(() => {
|
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
|
let isMounted = true
|
||||||
|
|
||||||
const loadCollaborators = async () => {
|
const loadCollaborators = async () => {
|
||||||
if (note.userId && isMounted) {
|
if (note.userId && isMounted) {
|
||||||
try {
|
try {
|
||||||
const users = await getNoteAllUsers(note.id)
|
const users = await getNoteAllUsers(note.id)
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setCollaborators(users)
|
setCollaborators(users)
|
||||||
// Owner is always first in the list
|
|
||||||
if (users.length > 0) {
|
if (users.length > 0) {
|
||||||
setOwner(users[0])
|
setOwner(users[0])
|
||||||
}
|
}
|
||||||
@@ -209,11 +222,11 @@ export const NoteCard = memo(function NoteCard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadCollaborators()
|
loadCollaborators()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isMounted = false
|
isMounted = false
|
||||||
}
|
}
|
||||||
}, [note.id, note.userId])
|
}, [note.id, note.userId, isSharedNote, currentUserId, session?.user])
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (confirm(t('notes.confirmDelete'))) {
|
if (confirm(t('notes.confirmDelete'))) {
|
||||||
|
|||||||
Reference in New Issue
Block a user