mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-04-21 06:59:17 -04:00
Fix hook deps and memoize ListView core row model
This commit is contained in:
@@ -135,7 +135,8 @@ export function ExplorerProvider({ children, spaceItemId: initialSpaceItemId }:
|
||||
setViewModeInternal(prefs.viewMode);
|
||||
setViewSettingsInternal(prefs.viewSettings);
|
||||
}
|
||||
}, [spaceItemKey, viewPrefs]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [spaceItemKey]);
|
||||
|
||||
// Load sort preferences when path changes
|
||||
useEffect(() => {
|
||||
@@ -143,19 +144,22 @@ export function ExplorerProvider({ children, spaceItemId: initialSpaceItemId }:
|
||||
if (sortPref) {
|
||||
setSortByInternal(sortPref as DirectorySortBy | MediaSortBy);
|
||||
}
|
||||
}, [pathKey, sortPrefs]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathKey]);
|
||||
|
||||
// Wrapper for setViewMode that persists to store
|
||||
const setViewMode = useCallback((mode: "grid" | "list" | "media" | "column" | "size" | "knowledge") => {
|
||||
setViewModeInternal(mode);
|
||||
viewPrefs.setPreferences(spaceItemKey, { viewMode: mode });
|
||||
}, [spaceItemKey, viewPrefs]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [spaceItemKey]);
|
||||
|
||||
// Wrapper for setSortBy that persists to store
|
||||
const setSortBy = useCallback((sort: DirectorySortBy | MediaSortBy) => {
|
||||
setSortByInternal(sort);
|
||||
sortPrefs.setPreferences(pathKey, sort);
|
||||
}, [pathKey, sortPrefs]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathKey]);
|
||||
|
||||
// Update sort when switching to media view
|
||||
useEffect(() => {
|
||||
@@ -166,7 +170,8 @@ export function ExplorerProvider({ children, spaceItemId: initialSpaceItemId }:
|
||||
setSortByInternal("modified");
|
||||
sortPrefs.setPreferences(pathKey, "modified");
|
||||
}
|
||||
}, [viewMode, sortByInternal, pathKey, sortPrefs]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [viewMode, sortByInternal, pathKey]);
|
||||
|
||||
const setViewSettings = useCallback((settings: Partial<ViewSettings>) => {
|
||||
setViewSettingsInternal((prev) => {
|
||||
@@ -174,7 +179,8 @@ export function ExplorerProvider({ children, spaceItemId: initialSpaceItemId }:
|
||||
viewPrefs.setPreferences(spaceItemKey, { viewSettings: updated });
|
||||
return updated;
|
||||
});
|
||||
}, [spaceItemKey, viewPrefs]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [spaceItemKey]);
|
||||
|
||||
const devicesQuery = useLibraryQuery({
|
||||
type: "devices.list",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef, useEffect, memo } from "react";
|
||||
import { useCallback, useRef, useEffect, memo, useMemo } from "react";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { flexRender } from "@tanstack/react-table";
|
||||
import { CaretDown } from "@phosphor-icons/react";
|
||||
@@ -26,19 +26,28 @@ export const ListView = memo(function ListView() {
|
||||
const headerScrollRef = useRef<HTMLDivElement>(null);
|
||||
const bodyScrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Memoize query input to prevent unnecessary re-fetches
|
||||
const queryInput = useMemo(
|
||||
() =>
|
||||
currentPath
|
||||
? {
|
||||
path: currentPath,
|
||||
limit: null,
|
||||
include_hidden: false,
|
||||
sort_by: sortBy as DirectorySortBy,
|
||||
}
|
||||
: null!,
|
||||
[currentPath, sortBy]
|
||||
);
|
||||
|
||||
const pathScope = useMemo(() => currentPath ?? undefined, [currentPath]);
|
||||
|
||||
const directoryQuery = useNormalizedCache({
|
||||
wireMethod: "query:files.directory_listing",
|
||||
input: currentPath
|
||||
? {
|
||||
path: currentPath,
|
||||
limit: null,
|
||||
include_hidden: false,
|
||||
sort_by: sortBy as DirectorySortBy,
|
||||
}
|
||||
: null!,
|
||||
input: queryInput,
|
||||
resourceType: "file",
|
||||
enabled: !!currentPath,
|
||||
pathScope: currentPath ?? undefined,
|
||||
pathScope,
|
||||
});
|
||||
|
||||
const files = directoryQuery.data?.files || [];
|
||||
@@ -64,37 +73,44 @@ export const ListView = memo(function ListView() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Keyboard navigation
|
||||
// Store values in refs to avoid effect re-runs
|
||||
const rowVirtualizerRef = useRef(rowVirtualizer);
|
||||
rowVirtualizerRef.current = rowVirtualizer;
|
||||
const filesRef = useRef(files);
|
||||
filesRef.current = files;
|
||||
|
||||
// Keyboard navigation - stable effect, uses refs for changing values
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
const direction = e.key === "ArrowDown" ? "down" : "up";
|
||||
const currentFiles = filesRef.current;
|
||||
|
||||
const currentIndex = focusedIndex >= 0 ? focusedIndex : 0;
|
||||
const newIndex =
|
||||
direction === "down"
|
||||
? Math.min(currentIndex + 1, files.length - 1)
|
||||
? Math.min(currentIndex + 1, currentFiles.length - 1)
|
||||
: Math.max(currentIndex - 1, 0);
|
||||
|
||||
if (e.shiftKey) {
|
||||
// Range selection with shift
|
||||
if (newIndex !== focusedIndex && files[newIndex]) {
|
||||
selectFile(files[newIndex], files, false, true);
|
||||
if (newIndex !== focusedIndex && currentFiles[newIndex]) {
|
||||
selectFile(currentFiles[newIndex], currentFiles, false, true);
|
||||
setFocusedIndex(newIndex);
|
||||
}
|
||||
} else {
|
||||
moveFocus(direction, files);
|
||||
moveFocus(direction, currentFiles);
|
||||
}
|
||||
|
||||
// Scroll to keep selection visible
|
||||
rowVirtualizer.scrollToIndex(newIndex, { align: "auto" });
|
||||
rowVirtualizerRef.current.scrollToIndex(newIndex, { align: "auto" });
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [focusedIndex, files, selectFile, setFocusedIndex, moveFocus, rowVirtualizer]);
|
||||
}, [focusedIndex, selectFile, setFocusedIndex, moveFocus]);
|
||||
|
||||
// Column sorting handler
|
||||
const handleHeaderClick = useCallback(
|
||||
|
||||
@@ -16,6 +16,9 @@ export const TABLE_HEADER_HEIGHT = 32;
|
||||
|
||||
// Column definitions for the list view
|
||||
export function useTable(files: File[]) {
|
||||
// Memoize files array reference to prevent unnecessary table updates
|
||||
const stableFiles = useMemo(() => files, [JSON.stringify(files.map(f => f.id))]);
|
||||
|
||||
const columns = useMemo<ColumnDef<File>[]>(
|
||||
() => [
|
||||
{
|
||||
@@ -55,14 +58,16 @@ export function useTable(files: File[]) {
|
||||
[]
|
||||
);
|
||||
|
||||
const coreRowModel = useMemo(() => getCoreRowModel<File>(), []);
|
||||
|
||||
const table = useReactTable({
|
||||
data: files,
|
||||
data: stableFiles,
|
||||
columns,
|
||||
defaultColumn: {
|
||||
minSize: 60,
|
||||
maxSize: 500,
|
||||
},
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getCoreRowModel: coreRowModel,
|
||||
columnResizeMode: "onChange",
|
||||
getRowId: (row) => row.id,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user