Skip to content

Commit 17ce398

Browse files
committed
fix(perf): remove auto-refetch on folder change, add smart folder refresh
1 parent 8fa7ed3 commit 17ce398

File tree

1 file changed

+62
-13
lines changed

1 file changed

+62
-13
lines changed

src/hooks/useNotes.ts

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,16 @@ export function useNotes() {
246246
}
247247
}, [clerkUser]);
248248

249-
const fetchAllNotes = useCallback(async (): Promise<Note[]> => {
249+
const fetchAllNotes = useCallback(async (folderId?: string): Promise<Note[]> => {
250+
// Build params - optionally filter by folder
251+
const baseParams: { page: number; limit: number; folderId?: string } = { page: 1, limit: 50 };
252+
if (folderId) {
253+
baseParams.folderId = folderId;
254+
}
255+
250256
// Fetch first page to determine total pages
251257
const firstPageResponse = await retryWithBackoff(() =>
252-
api.getNotes({ page: 1, limit: 50 })
258+
api.getNotes(baseParams)
253259
);
254260
const firstPageNotes = firstPageResponse.notes.map(convertApiNote);
255261

@@ -269,7 +275,7 @@ export function useNotes() {
269275
const batchPromises = [];
270276
for (let page = i; page < i + batchSize && page <= Math.min(totalPages, 50); page++) {
271277
batchPromises.push(
272-
retryWithBackoff(() => api.getNotes({ page, limit: 50 }))
278+
retryWithBackoff(() => api.getNotes({ ...baseParams, page, limit: 50 }))
273279
.then(response => response.notes.map(convertApiNote))
274280
);
275281
}
@@ -329,6 +335,54 @@ export function useNotes() {
329335
}
330336
}, [fetchAllFolders, fetchAllNotes]);
331337

338+
// Refresh only notes in the current folder (and subfolders) for better performance
339+
const refreshCurrentFolder = useCallback(async () => {
340+
if (!selectedFolder) {
341+
// No folder selected, do full refresh
342+
return loadData();
343+
}
344+
345+
try {
346+
setLoading(true);
347+
setError(null);
348+
349+
// Get all folder IDs to fetch (selected + descendants)
350+
const folderIds = [selectedFolder.id, ...getDescendantIds(selectedFolder.id, folders)];
351+
352+
// Fetch notes for each folder in parallel
353+
const folderNotesPromises = folderIds.map(folderId => fetchAllNotes(folderId));
354+
const folderNotesArrays = await Promise.all(folderNotesPromises);
355+
const fetchedNotes = folderNotesArrays.flat();
356+
357+
// Create a folder map for quick lookup
358+
const folderMap = new Map(folders.map((f) => [f.id, f]));
359+
360+
// Process fetched notes
361+
const processedNotes = fetchedNotes.map((note) => {
362+
const folder = note.folderId ? folderMap.get(note.folderId) : undefined;
363+
return {
364+
...note,
365+
attachments: [],
366+
attachmentCount: note.attachmentCount ?? 0,
367+
folder
368+
};
369+
});
370+
371+
// Merge with existing notes: replace notes in the fetched folders, keep others
372+
setNotes(prev => {
373+
// Remove old notes from the fetched folders
374+
const notesOutsideFolders = prev.filter(note => !folderIds.includes(note.folderId ?? ''));
375+
// Add the freshly fetched notes
376+
return [...notesOutsideFolders, ...processedNotes];
377+
});
378+
} catch (error) {
379+
secureLogger.error('Folder refresh failed', error);
380+
setError('Failed to refresh folder');
381+
} finally {
382+
setLoading(false);
383+
}
384+
}, [selectedFolder, folders, fetchAllNotes, loadData]);
385+
332386
useEffect(() => {
333387
if (!isLoaded) return;
334388

@@ -426,19 +480,14 @@ export function useNotes() {
426480
// }
427481
// }, [selectedNote?.id, webSocket.isAuthenticated, webSocket]);
428482

429-
// Refetch notes when switching folders to check for new notes from other devices/users
483+
// REMOVED: Auto-refetch on folder change was causing performance issues for users with many notes
484+
// Filtering now happens purely client-side. Use the refresh button to sync new notes from server.
485+
// The isInitialFolderChange ref is kept for potential future use.
430486
useEffect(() => {
431-
// Skip the initial mount to avoid duplicate fetches
432487
if (isInitialFolderChange.current) {
433488
isInitialFolderChange.current = false;
434-
return;
435-
}
436-
437-
// Only refetch if a folder is selected and encryption is ready
438-
if (selectedFolder?.id && encryptionReady) {
439-
void loadData();
440489
}
441-
}, [selectedFolder?.id, encryptionReady, loadData]);
490+
}, [selectedFolder?.id]);
442491

443492
// Load attachments on-demand when a note is selected
444493
useEffect(() => {
@@ -828,7 +877,7 @@ export function useNotes() {
828877
setSearchQuery,
829878
setNotes,
830879
setFolders,
831-
refetch: loadData,
880+
refetch: refreshCurrentFolder, // Smart refresh: folder-only when in folder, full refresh otherwise
832881
reinitialize: async () => {
833882
if (clerkUser) {
834883
await initializeUser();

0 commit comments

Comments
 (0)