page-tree-update.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { useCallback, useEffect } from 'react';
  2. import type { TreeInstance } from '@headless-tree/core';
  3. import { atom, useAtomValue, useSetAtom } from 'jotai';
  4. import { ROOT_PAGE_VIRTUAL_ID } from '../../constants';
  5. import { invalidatePageTreeChildren } from '../services';
  6. // Update generation number
  7. const generationAtom = atom<number>(1);
  8. // Array of IDs for last updated items
  9. // null is a special value meaning full tree update
  10. const lastUpdatedItemIdsAtom = atom<string[] | null>(null);
  11. // Read-only hooks
  12. export const usePageTreeInformationGeneration = () =>
  13. useAtomValue(generationAtom);
  14. export const usePageTreeInformationLastUpdatedItemIds = () =>
  15. useAtomValue(lastUpdatedItemIdsAtom);
  16. // Hook for notifying tree updates
  17. export const usePageTreeInformationUpdate = () => {
  18. const setGeneration = useSetAtom(generationAtom);
  19. const setLastUpdatedIds = useSetAtom(lastUpdatedItemIdsAtom);
  20. // Notify update for specific items
  21. const notifyUpdateItems = useCallback(
  22. (itemIds: string[]) => {
  23. setLastUpdatedIds(itemIds);
  24. setGeneration((prev) => prev + 1);
  25. },
  26. [setGeneration, setLastUpdatedIds],
  27. );
  28. // Notify update for all trees
  29. const notifyUpdateAllTrees = useCallback(() => {
  30. setLastUpdatedIds(null);
  31. setGeneration((prev) => prev + 1);
  32. }, [setGeneration, setLastUpdatedIds]);
  33. return {
  34. notifyUpdateItems,
  35. notifyUpdateAllTrees,
  36. };
  37. };
  38. export const usePageTreeRevalidationEffect = (
  39. tree: TreeInstance<unknown>,
  40. generation: number,
  41. opts?: { onRevalidated?: () => void },
  42. ) => {
  43. const globalGeneration = useAtomValue(generationAtom);
  44. const globalLastUpdatedItemIds = useAtomValue(lastUpdatedItemIdsAtom);
  45. const { getItemInstance, rebuildTree } = tree;
  46. useEffect(() => {
  47. if (globalGeneration <= generation) return; // Already up to date
  48. // Determine update scope
  49. const shouldUpdateAll = globalLastUpdatedItemIds == null;
  50. if (shouldUpdateAll) {
  51. // Full tree update: clear all cache and refetch from root
  52. invalidatePageTreeChildren();
  53. const root = getItemInstance(ROOT_PAGE_VIRTUAL_ID);
  54. root?.invalidateChildrenIds(true);
  55. } else {
  56. // Partial update: clear cache for specified items and refetch children
  57. invalidatePageTreeChildren(globalLastUpdatedItemIds);
  58. globalLastUpdatedItemIds.forEach((itemId) => {
  59. const item = getItemInstance(itemId);
  60. // Invalidate children to refresh child list
  61. item?.invalidateChildrenIds(true);
  62. });
  63. }
  64. // Rebuild tree after a short delay to allow async data fetching to complete
  65. // This ensures isItemFolder is re-evaluated with fresh children data
  66. const timeoutId = setTimeout(() => {
  67. rebuildTree();
  68. }, 100);
  69. opts?.onRevalidated?.();
  70. return () => clearTimeout(timeoutId);
  71. }, [
  72. globalGeneration,
  73. generation,
  74. getItemInstance,
  75. globalLastUpdatedItemIds,
  76. rebuildTree,
  77. opts,
  78. ]);
  79. };