refactor(doclist): remove loading state and unify navigation prefetch behavior

Eliminate unused loading state and related effects from document tile and list
row components. Standardize navigation by disabling prefetch on all document
open links in doclist views for consistent client-side routing. Cache
document list state on load to improve state restoration and initialization.
This commit is contained in:
Richard R 2026-05-28 09:15:18 -06:00
parent eff25ad8df
commit b9490a53ac
5 changed files with 42 additions and 39 deletions

View file

@ -36,6 +36,8 @@ import { ListView } from './views/ListView';
import { ColumnsView } from './views/ColumnsView';
import { GalleryView } from './views/GalleryView';
let cachedDocumentListState: DocumentListState | null = null;
type DocumentToDelete = {
id: string;
name: string;
@ -116,22 +118,25 @@ interface DocumentListInnerProps {
}
function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
const [sortBy, setSortBy] = useState<SortBy>(DEFAULT_STATE.sortBy);
const cachedState = cachedDocumentListState;
const [sortBy, setSortBy] = useState<SortBy>(cachedState?.sortBy ?? DEFAULT_STATE.sortBy);
const [sortDirection, setSortDirection] = useState<SortDirection>(
DEFAULT_STATE.sortDirection,
cachedState?.sortDirection ?? DEFAULT_STATE.sortDirection,
);
const [viewMode, setViewMode] = useState<ViewMode>(DEFAULT_STATE.viewMode);
const [iconSize, setIconSize] = useState<IconSize>(DEFAULT_STATE.iconSize);
const [folders, setFolders] = useState<Folder[]>(DEFAULT_STATE.folders);
const [showHint, setShowHint] = useState(true);
const [sidebarWidth, setSidebarWidth] = useState(DEFAULT_STATE.sidebarWidth);
const [sidebarFilter, setSidebarFilter] = useState<SidebarFilter>('all');
const [sidebarOpen, setSidebarOpen] = useState(true);
const [viewMode, setViewMode] = useState<ViewMode>(
normalizeViewMode(cachedState?.viewMode ?? DEFAULT_STATE.viewMode),
);
const [iconSize, setIconSize] = useState<IconSize>(cachedState?.iconSize ?? DEFAULT_STATE.iconSize);
const [folders, setFolders] = useState<Folder[]>(cachedState?.folders ?? DEFAULT_STATE.folders);
const [showHint, setShowHint] = useState(cachedState?.showHint ?? true);
const [sidebarWidth, setSidebarWidth] = useState(cachedState?.sidebarWidth ?? DEFAULT_STATE.sidebarWidth);
const [sidebarFilter, setSidebarFilter] = useState<SidebarFilter>(cachedState?.sidebarFilter ?? 'all');
const [sidebarOpen, setSidebarOpen] = useState(!(cachedState?.sidebarCollapsed ?? false));
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
const [query, setQuery] = useState('');
const [recentlyOpenedById, setRecentlyOpenedById] = useState<Record<string, number>>({});
const [isInitialized, setIsInitialized] = useState(false);
const [isInitialized, setIsInitialized] = useState(cachedState !== null);
const [documentToDelete, setDocumentToDelete] = useState<DocumentToDelete | null>(null);
const [pendingMerge, setPendingMerge] = useState<
@ -164,6 +169,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
const saved = await getDocumentListState();
if (cancelled) return;
if (saved) {
cachedDocumentListState = saved;
setSortBy(saved.sortBy);
setSortDirection(saved.sortDirection);
setFolders(saved.folders ?? []);
@ -173,6 +179,17 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
setSidebarWidth(saved.sidebarWidth ?? DEFAULT_STATE.sidebarWidth);
setSidebarFilter(saved.sidebarFilter ?? 'all');
setSidebarOpen(!(saved.sidebarCollapsed ?? false));
} else {
cachedDocumentListState = null;
setSortBy(DEFAULT_STATE.sortBy);
setSortDirection(DEFAULT_STATE.sortDirection);
setFolders(DEFAULT_STATE.folders);
setShowHint(DEFAULT_STATE.showHint);
setViewMode(DEFAULT_STATE.viewMode);
setIconSize(DEFAULT_STATE.iconSize);
setSidebarWidth(DEFAULT_STATE.sidebarWidth);
setSidebarFilter(DEFAULT_STATE.sidebarFilter);
setSidebarOpen(!DEFAULT_STATE.sidebarCollapsed);
}
setIsInitialized(true);
})();
@ -196,6 +213,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
sidebarFilter,
sidebarCollapsed: !sidebarOpen,
};
cachedDocumentListState = state;
void saveDocumentListState(state);
}, [
sortBy,

View file

@ -102,6 +102,9 @@ export function ColumnsView({
}: ColumnsViewProps) {
const { setVisibleOrder } = useDocumentSelection();
const [selectedDoc, setSelectedDoc] = useState<DocumentListDocument | null>(null);
const openHref = selectedDoc
? `/${selectedDoc.type}/${encodeURIComponent(selectedDoc.id)}`
: null;
useEffect(() => {
setVisibleOrder(documents);
@ -154,7 +157,8 @@ export function ColumnsView({
</p>
<div className="mt-3 flex gap-2">
<Link
href={`/${selectedDoc.type}/${encodeURIComponent(selectedDoc.id)}`}
href={openHref || '/app'}
prefetch={false}
className="flex-1 inline-flex items-center justify-center h-8 rounded-md bg-accent text-background text-[12px] font-medium hover:bg-secondary-accent hover:scale-[1.01] transition-all duration-200 ease-out"
>
Open

View file

@ -1,7 +1,6 @@
'use client';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { useDrag, useDrop, type DragSourceMonitor } from 'react-dnd';
import { Button } from '@headlessui/react';
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
@ -70,7 +69,6 @@ export function DocumentTile({
onDelete,
onMergeIntoFolder,
}: DocumentTileProps) {
const [loading, setLoading] = useState(false);
const { authEnabled } = useAuthConfig();
const { data: session } = useAuthSession();
const href = `/${doc.type}/${encodeURIComponent(doc.id)}`;
@ -136,19 +134,9 @@ export function DocumentTile({
if (e.shiftKey || e.metaKey || e.ctrlKey) {
e.preventDefault();
selection.select(doc, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
return;
}
selection.replace([doc]);
setLoading(true);
};
// Cap any stuck loading flag if the user navigates back.
useEffect(() => {
if (!loading) return;
const t = setTimeout(() => setLoading(false), 2500);
return () => clearTimeout(t);
}, [loading]);
return (
<div
ref={setRefs}
@ -160,12 +148,12 @@ export function DocumentTile({
? 'border-accent bg-offbase'
: 'border-offbase bg-base hover:bg-offbase hover:border-accent') +
(isDropTarget ? ' ring-1 ring-accent' : '') +
(isDragging ? ' opacity-50' : '') +
(loading ? ' prism-outline' : '')
(isDragging ? ' opacity-50' : '')
}
>
<Link
href={href}
prefetch={false}
draggable={false}
className="block"
aria-label={`Open ${doc.name}`}
@ -176,6 +164,7 @@ export function DocumentTile({
<div className={`flex items-center w-full ${BOTTOM_PADDING_CLASSES[iconSize]}`}>
<Link
href={href}
prefetch={false}
draggable={false}
className={`flex items-center flex-1 min-w-0 rounded-md ${LINK_PADDING_CLASS} ${GAP_CLASSES[iconSize]}`}
onClick={handleClick}

View file

@ -114,6 +114,7 @@ export function GalleryView({
const railRef = useRef<HTMLDivElement | null>(null);
const [activeIdx, setActiveIdx] = useState(0);
const activeDoc = useMemo(() => documents[activeIdx], [documents, activeIdx]);
const openHref = activeDoc ? `/${activeDoc.type}/${encodeURIComponent(activeDoc.id)}` : null;
useEffect(() => {
setVisibleOrder(documents);
@ -163,7 +164,8 @@ export function GalleryView({
</div>
<div className="flex gap-2">
<Link
href={`/${activeDoc.type}/${encodeURIComponent(activeDoc.id)}`}
href={openHref || '/app'}
prefetch={false}
className="h-8 px-4 inline-flex items-center justify-center rounded-md bg-accent text-background text-[12px] font-medium hover:bg-secondary-accent hover:scale-[1.01] transition-all duration-200 ease-out"
>
Open

View file

@ -1,7 +1,7 @@
'use client';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { Button } from '@headlessui/react';
import type {
@ -87,7 +87,6 @@ function DocRow({
const selection = useDocumentSelection();
const isSelected = selection.isSelected(doc);
const isInFolder = Boolean(doc.folderId);
const [loading, setLoading] = useState(false);
const href = `/${doc.type}/${encodeURIComponent(doc.id)}`;
const [{ isDragging }, dragRef] = useDrag<DocumentDragItem, void, { isDragging: boolean }>(() => ({
@ -117,22 +116,13 @@ function DocRow({
dropRef(node);
};
useEffect(() => {
if (!loading) return;
const t = setTimeout(() => setLoading(false), 2500);
return () => clearTimeout(t);
}, [loading]);
const isTarget = isOver && canDrop;
const handleClick: React.MouseEventHandler = (e) => {
if (e.shiftKey || e.metaKey || e.ctrlKey) {
e.preventDefault();
selection.select(doc, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
return;
}
selection.replace([doc]);
setLoading(true);
};
return (
@ -146,12 +136,12 @@ function DocRow({
? 'bg-offbase text-accent'
: 'text-foreground hover:bg-offbase') +
(isTarget ? ' ring-1 ring-accent ring-inset' : '') +
(isDragging ? ' opacity-50' : '') +
(loading ? ' prism-outline' : '')
(isDragging ? ' opacity-50' : '')
}
>
<Link
href={href}
prefetch={false}
draggable={false}
onClick={handleClick}
className="flex items-center gap-2 min-w-0 px-2 py-1.5"