Просмотр исходного кода

refactor: update page tree hooks and revalidation logic

Yuki Takei 4 месяцев назад
Родитель
Сommit
776006ed33

+ 6 - 30
apps/app/src/client/components/ItemsTree/SimplifiedItemsTree.tsx

@@ -10,7 +10,7 @@ import { useVirtualizer } from '@tanstack/react-virtual';
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { ROOT_PAGE_VIRTUAL_ID } from '~/constants/page-tree';
 import type { IPageForTreeItem } from '~/interfaces/page';
-import { useTreeUpdateGeneration, useLastUpdatedItemIds } from '~/states/page-tree-update';
+import { usePageTreeInformationGeneration, usePageTreeRevalidationEffect } from '~/states/page-tree-update';
 import { useSWRxRootPage } from '~/stores/page-listing';
 
 import type { TreeItemProps } from '../TreeItem';
@@ -105,39 +105,15 @@ export const SimplifiedItemsTree: FC<Props> = (props: Props) => {
     features: [asyncDataLoaderFeature],
   });
 
-  // Extract getItemInstance for dependency array
-  const getItemInstance = tree.getItemInstance;
-
   // Track local generation number
   const [localGeneration, setLocalGeneration] = useState(1);
-  const globalGeneration = useTreeUpdateGeneration();
-  const lastUpdatedItemIds = useLastUpdatedItemIds();
+  const globalGeneration = usePageTreeInformationGeneration();
 
   // Refetch data when global generation is updated
-  useEffect(() => {
-    if (globalGeneration <= localGeneration) {
-      return; // Already up to date
-    }
-
-    // Determine update scope
-    const shouldUpdateAll = lastUpdatedItemIds.includes('*');
-
-    if (shouldUpdateAll) {
-      // Full tree update: refetch from root
-      const root = getItemInstance(ROOT_PAGE_VIRTUAL_ID);
-      root?.invalidateChildrenIds(true);
-    }
-    else {
-      // Partial update: refetch children of specified items
-      lastUpdatedItemIds.forEach((itemId) => {
-        const item = getItemInstance(itemId);
-        item?.invalidateChildrenIds(true);
-      });
-    }
-
-    // Update local generation
-    setLocalGeneration(globalGeneration);
-  }, [globalGeneration, localGeneration, lastUpdatedItemIds, getItemInstance]);
+  usePageTreeRevalidationEffect(tree, localGeneration, {
+    // Update local generation number after revalidation
+    onRevalidated: () => setLocalGeneration(globalGeneration),
+  });
 
   const items = tree.getItems();
 

+ 6 - 6
apps/app/src/client/components/Sidebar/PageTreeItem/SimplifiedPageTreeItem.tsx

@@ -8,7 +8,7 @@ import { useRouter } from 'next/router';
 
 import { ROOT_PAGE_VIRTUAL_ID } from '~/constants/page-tree';
 import type { IPageForItem } from '~/interfaces/page';
-import { useNotifyTreeUpdate } from '~/states/page-tree-update';
+import { usePageTreeInformationUpdate } from '~/states/page-tree-update';
 import { usePageDeleteModalActions } from '~/states/ui/modal/page-delete';
 import type { IPageForPageDuplicateModal } from '~/states/ui/modal/page-duplicate';
 import { usePageDuplicateModalActions } from '~/states/ui/modal/page-duplicate';
@@ -42,27 +42,27 @@ export const SimplifiedPageTreeItem: FC<TreeItemProps> = ({
 
   const { open: openDuplicateModal } = usePageDuplicateModalActions();
   const { open: openDeleteModal } = usePageDeleteModalActions();
-  const { notifyItemsUpdated } = useNotifyTreeUpdate();
+  const { notifyUpdateItems } = usePageTreeInformationUpdate();
 
   const onClickDuplicateMenuItem = useCallback((page: IPageForPageDuplicateModal) => {
     openDuplicateModal(page, {
       onDuplicated: () => {
         // Notify headless-tree update
         const parentId = item.parent != null ? getIdStringForRef(item.parent) : ROOT_PAGE_VIRTUAL_ID;
-        notifyItemsUpdated([parentId]);
+        notifyUpdateItems([parentId]);
       },
     });
-  }, [openDuplicateModal, notifyItemsUpdated, item.parent]);
+  }, [openDuplicateModal, notifyUpdateItems, item.parent]);
 
   const onClickDeleteMenuItem = useCallback((page: IPageToDeleteWithMeta) => {
     openDeleteModal([page], {
       onDeleted: () => {
         // Notify headless-tree update
         const parentId = item.parent != null ? getIdStringForRef(item.parent) : ROOT_PAGE_VIRTUAL_ID;
-        notifyItemsUpdated([parentId]);
+        notifyUpdateItems([parentId]);
       },
     });
-  }, [openDeleteModal, item.parent, notifyItemsUpdated]);
+  }, [openDeleteModal, item.parent, notifyUpdateItems]);
 
   const { Control } = usePageItemControl();
 

+ 56 - 17
apps/app/src/states/page-tree-update.ts

@@ -1,29 +1,30 @@
-import { useCallback } from 'react';
+import { useCallback, useEffect } from 'react';
+import type { TreeInstance } from '@headless-tree/core';
 import { atom, useAtomValue, useSetAtom } from 'jotai';
 
+import { ROOT_PAGE_VIRTUAL_ID } from '~/constants/page-tree';
+
 // Update generation number
-const treeUpdateGenerationAtom = atom<number>(1);
+const generationAtom = atom<number>(1);
 
 // Array of IDs for last updated items
-// ['*'] is a special value meaning full tree update
-const lastUpdatedItemIdsAtom = atom<string[]>([]);
+// null is a special value meaning full tree update
+const lastUpdatedItemIdsAtom = atom<string[] | null>(null);
 
 // Read-only hooks
-export const useTreeUpdateGeneration = () => {
-  return useAtomValue(treeUpdateGenerationAtom);
-};
+export const usePageTreeInformationGeneration = () =>
+  useAtomValue(generationAtom);
 
-export const useLastUpdatedItemIds = () => {
-  return useAtomValue(lastUpdatedItemIdsAtom);
-};
+export const usePageTreeInformationLastUpdatedItemIds = () =>
+  useAtomValue(lastUpdatedItemIdsAtom);
 
 // Hook for notifying tree updates
-export const useNotifyTreeUpdate = () => {
-  const setGeneration = useSetAtom(treeUpdateGenerationAtom);
+export const usePageTreeInformationUpdate = () => {
+  const setGeneration = useSetAtom(generationAtom);
   const setLastUpdatedIds = useSetAtom(lastUpdatedItemIdsAtom);
 
   // Notify update for specific items
-  const notifyItemsUpdated = useCallback(
+  const notifyUpdateItems = useCallback(
     (itemIds: string[]) => {
       setLastUpdatedIds(itemIds);
       setGeneration((prev) => prev + 1);
@@ -32,13 +33,51 @@ export const useNotifyTreeUpdate = () => {
   );
 
   // Notify update for all trees
-  const notifyAllTreesUpdated = useCallback(() => {
-    setLastUpdatedIds(['*']);
+  const notifyUpdateAllTrees = useCallback(() => {
+    setLastUpdatedIds(null);
     setGeneration((prev) => prev + 1);
   }, [setGeneration, setLastUpdatedIds]);
 
   return {
-    notifyItemsUpdated,
-    notifyAllTreesUpdated,
+    notifyUpdateItems,
+    notifyUpdateAllTrees,
   };
 };
+
+export const usePageTreeRevalidationEffect = (
+  tree: TreeInstance<unknown>,
+  generation: number,
+  opts?: { onRevalidated?: () => void },
+) => {
+  const globalGeneration = useAtomValue(generationAtom);
+  const globalLastUpdatedItemIds = useAtomValue(lastUpdatedItemIdsAtom);
+
+  const { getItemInstance } = tree;
+
+  useEffect(() => {
+    if (globalGeneration <= generation) return; // Already up to date
+
+    // Determine update scope
+    const shouldUpdateAll = globalLastUpdatedItemIds == null;
+
+    if (shouldUpdateAll) {
+      // Full tree update: refetch from root
+      const root = getItemInstance(ROOT_PAGE_VIRTUAL_ID);
+      root?.invalidateChildrenIds(true);
+    } else {
+      // Partial update: refetch children of specified items
+      globalLastUpdatedItemIds.forEach((itemId) => {
+        const item = getItemInstance(itemId);
+        item?.invalidateChildrenIds(true);
+      });
+    }
+
+    opts?.onRevalidated?.();
+  }, [
+    globalGeneration,
+    generation,
+    getItemInstance,
+    globalLastUpdatedItemIds,
+    opts,
+  ]);
+};