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

refactor current page data fetcher

Yuki Takei 8 месяцев назад
Родитель
Сommit
87e0522285

+ 3 - 3
apps/app/docs/plan/page-state-jotai-migration.md

@@ -25,7 +25,7 @@ SWRとJotaiの併用により、それぞれの長所を活かしつつ問題を
 ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
 │   Components    │◄───┤  Jotai Atoms    │◄───┤ SWR Fetchers    │
 │                 │    │                 │    │                 │
-│ - usePageData   │    │ - pageAtom      │    │ - usePageFetcher
+│ - usePageData   │    │ - pageAtom      │    │ - useFetchCurrentPage
 │ - usePageId     │    │ - pageIdAtom    │    │ - API calls     │
 │ - usePagePath   │    │ - statusAtoms   │    │ - Error handling│
 └─────────────────┘    └─────────────────┘    └─────────────────┘
@@ -82,7 +82,7 @@ SWRとJotaiの併用により、それぞれの長所を活かしつつ問題を
 - **`useSWRMUTxCurrentPage`**: 複数箇所
   ```typescript
   // Before: { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage()
-  // After:  { fetchAndUpdatePage } = usePageFetcher()
+  // After:  { fetchCurrentPage } = useFetchCurrentPage()
   ```
 
 - **`useCurrentPagePath`**: 複数箇所(Deprecated)
@@ -111,7 +111,7 @@ SWRとJotaiの併用により、それぞれの長所を活かしつつ問題を
    import { useCurrentPageId, useSWRxCurrentPage, useSWRMUTxCurrentPage } from '~/stores/page';
    
    // After
-   import { useCurrentPageId, useCurrentPageData, usePageFetcher } from '~/states/page';
+   import { useCurrentPageId, useCurrentPageData, useFetchCurrentPage } from '~/states/page';
    ```
 
 2. **Hook使用箇所の一括変更(182箇所)**

+ 4 - 4
apps/app/src/client/components/Bookmarks/BookmarkItem.tsx

@@ -15,7 +15,7 @@ import { addBookmarkToFolder, renamePage } from '~/client/util/bookmark-utils';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import type { BookmarkFolderItems, DragItemDataType } from '~/interfaces/bookmark-info';
 import { DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
-import { usePageFetcher } from '~/states/page';
+import { useFetchCurrentPage } from '~/states/page';
 import { usePutBackPageModal } from '~/stores/modal';
 import { mutateAllPageInfo, useSWRxPageInfo } from '~/stores/page';
 
@@ -52,7 +52,7 @@ export const BookmarkItem = (props: Props): JSX.Element => {
   const [isRenameInputShown, setRenameInputShown] = useState(false);
 
   const { data: pageInfo, mutate: mutatePageInfo } = useSWRxPageInfo(bookmarkedPage?._id);
-  const { trigger: mutateCurrentPage } = usePageFetcher();
+  const { fetchAndUpdatePage } = useFetchCurrentPage();
 
   const paddingLeft = BASE_BOOKMARK_PADDING + (BASE_FOLDER_PADDING * (level));
   const dragItem: Partial<DragItemDataType> = {
@@ -146,7 +146,7 @@ export const BookmarkItem = (props: Props): JSX.Element => {
         mutateAllPageInfo();
         bookmarkFolderTreeMutation();
         router.push(`/${pageId}`);
-        mutateCurrentPage();
+        fetchAndUpdatePage();
         toastSuccess(t('page_has_been_reverted', { path }));
       }
       catch (err) {
@@ -154,7 +154,7 @@ export const BookmarkItem = (props: Props): JSX.Element => {
       }
     };
     openPutBackPageModal({ pageId, path }, { onPutBacked: putBackedHandler });
-  }, [bookmarkedPage, openPutBackPageModal, bookmarkFolderTreeMutation, router, mutateCurrentPage, t]);
+  }, [bookmarkedPage, openPutBackPageModal, bookmarkFolderTreeMutation, router, fetchAndUpdatePage, t]);
 
   if (bookmarkedPage == null) {
     return <></>;

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

@@ -12,7 +12,7 @@ import type { IPageForItem } from '~/interfaces/page';
 import type { OnDuplicatedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import type { UpdateDescCountData, UpdateDescCountRawData } from '~/interfaces/websocket';
 import { SocketEventName } from '~/interfaces/websocket';
-import { useCurrentPagePath, usePageFetcher } from '~/states/page';
+import { useCurrentPagePath, useFetchCurrentPage } from '~/states/page';
 import type { IPageForPageDuplicateModal } from '~/stores/modal';
 import { usePageDuplicateModal, usePageDeleteModal } from '~/stores/modal';
 import { mutateAllPageInfo } from '~/stores/page';
@@ -63,7 +63,7 @@ export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
   const { data: ptDescCountMap, update: updatePtDescCountMap } = usePageTreeDescCountMap();
 
   // for mutation
-  const { trigger: mutateCurrentPage } = usePageFetcher();
+  const { fetchCurrentPage } = useFetchCurrentPage();
 
   useEffect(() => {
     if (socket == null) {
@@ -87,9 +87,9 @@ export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
     mutatePageList();
 
     if (currentPagePath === fromPath || currentPagePath === toPath) {
-      mutateCurrentPage();
+      fetchCurrentPage();
     }
-  }, [currentPagePath, mutateCurrentPage]);
+  }, [currentPagePath, fetchCurrentPage]);
 
   const onClickDuplicateMenuItem = useCallback((pageToDuplicate: IPageForPageDuplicateModal) => {
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -123,13 +123,13 @@ export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
       mutateAllPageInfo();
 
       if (currentPagePath === pathOrPathsToDelete) {
-        mutateCurrentPage();
+        fetchCurrentPage();
         router.push(isCompletely ? path.dirname(pathOrPathsToDelete) : `/trash${pathOrPathsToDelete}`);
       }
     };
 
     openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler });
-  }, [currentPagePath, mutateCurrentPage, openDeleteModal, router, t]);
+  }, [currentPagePath, fetchCurrentPage, openDeleteModal, router, t]);
 
 
   if (error != null) {

+ 8 - 8
apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -24,7 +24,7 @@ import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import { usePageBulkExportSelectModal } from '~/features/page-bulk-export/client/stores/modal';
 import type { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
-import { useCurrentPageId, usePageFetcher } from '~/states/page';
+import { useCurrentPageId, useFetchCurrentPage } from '~/states/page';
 import {
   useCurrentPathname,
   useCurrentUser, useIsGuestUser, useIsReadOnlyUser, useIsBulkExportPagesEnabled,
@@ -255,7 +255,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const router = useRouter();
 
   const { data: shareLinkId } = useShareLinkId();
-  const { trigger: mutateCurrentPage } = usePageFetcher();
+  const { fetchCurrentPage } = useFetchCurrentPage();
 
   const { data: currentPathname } = useCurrentPathname();
   const isSharedPage = pagePathUtils.isSharedPage(currentPathname ?? '');
@@ -302,13 +302,13 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
   const renameItemClickedHandler = useCallback(async(page: IPageToRenameWithMeta<IPageInfoForEntity>) => {
     const renamedHandler: OnRenamedFunction = () => {
-      mutateCurrentPage();
+      fetchCurrentPage();
       mutatePageInfo();
       mutatePageTree();
       mutateRecentlyUpdated();
     };
     openRenameModal(page, { onRenamed: renamedHandler });
-  }, [mutateCurrentPage, mutatePageInfo, openRenameModal]);
+  }, [fetchCurrentPage, mutatePageInfo, openRenameModal]);
 
   const deleteItemClickedHandler = useCallback((pageWithMeta: IPageWithMeta) => {
     const deletedHandler: OnDeletedFunction = (pathOrPathsToDelete, isRecursively, isCompletely) => {
@@ -326,20 +326,20 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
         router.push(currentPathname);
       }
 
-      mutateCurrentPage();
+      fetchCurrentPage();
       mutatePageInfo();
       mutatePageTree();
       mutateRecentlyUpdated();
     };
     openDeleteModal([pageWithMeta], { onDeleted: deletedHandler });
-  }, [currentPathname, mutateCurrentPage, openDeleteModal, router, mutatePageInfo]);
+  }, [currentPathname, fetchCurrentPage, openDeleteModal, router, mutatePageInfo]);
 
   const switchContentWidthHandler = useCallback(async(pageId: string, value: boolean) => {
     if (!isSharedPage) {
       await updateContentWidth(pageId, value);
-      mutateCurrentPage();
+      fetchCurrentPage();
     }
-  }, [isSharedPage, mutateCurrentPage]);
+  }, [isSharedPage, fetchCurrentPage]);
 
   const additionalMenuItemsRenderer = useCallback(() => {
     if (revisionId == null || pageId == null) {

+ 4 - 4
apps/app/src/client/components/PageEditor/page-path-rename-utils.ts

@@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next';
 
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
-import { usePageFetcher } from '~/states/page';
+import { useFetchCurrentPage } from '~/states/page';
 import { mutatePageTree, mutatePageList, mutateRecentlyUpdated } from '~/stores/page-listing';
 import { useIsUntitledPage } from '~/stores/ui';
 
@@ -17,7 +17,7 @@ export const usePagePathRenameHandler = (
 ): PagePathRenameHandler => {
 
   const { t } = useTranslation();
-  const { fetchAndUpdatePage } = usePageFetcher();
+  const { fetchCurrentPage } = useFetchCurrentPage();
   const { mutate: mutateIsUntitledPage } = useIsUntitledPage();
 
   const pagePathRenameHandler = useCallback(async(newPagePath, onRenameFinish, onRenameFailure) => {
@@ -38,7 +38,7 @@ export const usePagePathRenameHandler = (
       mutateIsUntitledPage(false);
 
       if (currentPage.path === fromPath || currentPage.path === toPath) {
-        fetchAndUpdatePage();
+        fetchCurrentPage();
       }
     };
 
@@ -58,7 +58,7 @@ export const usePagePathRenameHandler = (
       onRenameFailure?.();
       toastError(err);
     }
-  }, [currentPage, mutateCurrentPage, mutateIsUntitledPage, t]);
+  }, [currentPage, fetchCurrentPage, mutateIsUntitledPage, t]);
 
   return pagePathRenameHandler;
 };

+ 4 - 4
apps/app/src/client/services/page-operation.ts

@@ -5,7 +5,7 @@ import { SubscriptionStatusType } from '@growi/core';
 import urljoin from 'url-join';
 
 import type { SyncLatestRevisionBody } from '~/interfaces/yjs';
-import { useCurrentPageId, usePageFetcher } from '~/states/page';
+import { useCurrentPageId, useFetchCurrentPage } from '~/states/page';
 import { useIsGuestUser } from '~/stores-universal/context';
 import { useEditingMarkdown, usePageTagsForEditors } from '~/stores/editor';
 import {
@@ -100,7 +100,7 @@ export type UpdateStateAfterSaveOption = {
 
 export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: UpdateStateAfterSaveOption): (() => Promise<void>) | undefined => {
   const [, setCurrentPageId] = useCurrentPageId();
-  const { fetchAndUpdatePage } = usePageFetcher();
+  const { fetchCurrentPage } = useFetchCurrentPage();
   const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
   const { mutate: mutateTagsInfo } = useSWRxTagsInfo(pageId);
   const { sync: syncTagsInfoForEditor } = usePageTagsForEditors(pageId);
@@ -119,7 +119,7 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
     syncTagsInfoForEditor(); // sync global state for client
 
     await setCurrentPageId(pageId);
-    const updatedPage = await fetchAndUpdatePage();
+    const updatedPage = await fetchCurrentPage();
 
     if (updatedPage == null || updatedPage.revision == null) { return }
 
@@ -143,7 +143,7 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
     setRemoteLatestPageData(remoterevisionData);
   },
   // eslint-disable-next-line max-len
-  [pageId, mutateTagsInfo, syncTagsInfoForEditor, setCurrentPageId, fetchAndUpdatePage, opts?.supressEditingMarkdownMutation, mutateCurrentGrantData, mutateApplicableGrant, setRemoteLatestPageData, mutateEditingMarkdown]);
+  [pageId, mutateTagsInfo, syncTagsInfoForEditor, setCurrentPageId, fetchCurrentPage, opts?.supressEditingMarkdownMutation, mutateCurrentGrantData, mutateApplicableGrant, setRemoteLatestPageData, mutateEditingMarkdown]);
 };
 
 export const unlink = async(path: string): Promise<void> => {

+ 4 - 4
apps/app/src/client/services/side-effects/page-updated.ts

@@ -3,7 +3,7 @@ import { useCallback, useEffect } from 'react';
 import { useGlobalSocket } from '@growi/core/dist/swr';
 
 import { SocketEventName } from '~/interfaces/websocket';
-import { useCurrentPageData, usePageFetcher } from '~/states/page';
+import { useCurrentPageData, useFetchCurrentPage } from '~/states/page';
 import { useEditorMode, EditorMode } from '~/stores-universal/ui';
 import { usePageStatusAlert } from '~/stores/alert';
 import { useSetRemoteLatestPageData, type RemoteRevisionData } from '~/stores/remote-latest-page';
@@ -16,7 +16,7 @@ export const usePageUpdatedEffect = (): void => {
   const { data: socket } = useGlobalSocket();
   const { data: editorMode } = useEditorMode();
   const [currentPage] = useCurrentPageData();
-  const { fetchAndUpdatePage } = usePageFetcher();
+  const { fetchCurrentPage } = useFetchCurrentPage();
   const { open: openPageStatusAlert, close: closePageStatusAlert } = usePageStatusAlert();
 
   const remotePageDataUpdateHandler = useCallback((data) => {
@@ -40,7 +40,7 @@ export const usePageUpdatedEffect = (): void => {
 
       // !!CAUTION!! Timing of calling openPageStatusAlert may clash with components/PageEditor/conflict.tsx
       if (isRevisionOutdated && editorMode === EditorMode.View) {
-        openPageStatusAlert({ hideEditorMode: EditorMode.Editor, onRefleshPage: fetchAndUpdatePage });
+        openPageStatusAlert({ hideEditorMode: EditorMode.Editor, onRefleshPage: fetchCurrentPage });
       }
 
       // Clear cache
@@ -48,7 +48,7 @@ export const usePageUpdatedEffect = (): void => {
         closePageStatusAlert();
       }
     }
-  }, [currentPage?._id, currentPage?.revision?._id, editorMode, fetchAndUpdatePage, openPageStatusAlert, closePageStatusAlert, setRemoteLatestPageData]);
+  }, [currentPage?._id, currentPage?.revision?._id, editorMode, fetchCurrentPage, openPageStatusAlert, closePageStatusAlert, setRemoteLatestPageData]);
 
   // listen socket for someone updating this page
   useEffect(() => {

+ 4 - 4
apps/app/src/components/PageView/PageAlerts/OldRevisionAlert.tsx

@@ -4,7 +4,7 @@ import { returnPathForURL } from '@growi/core/dist/utils/path-utils';
 import { useRouter } from 'next/router';
 import { useTranslation } from 'react-i18next';
 
-import { useCurrentPageData, useLatestRevision, usePageFetcher } from '~/states/page';
+import { useCurrentPageData, useLatestRevision, useFetchCurrentPage } from '~/states/page';
 
 export const OldRevisionAlert = (): JSX.Element => {
   const router = useRouter();
@@ -12,7 +12,7 @@ export const OldRevisionAlert = (): JSX.Element => {
 
   const [isOldRevisionPage] = useLatestRevision();
   const [page] = useCurrentPageData();
-  const { fetchAndUpdatePage } = usePageFetcher();
+  const { fetchCurrentPage } = useFetchCurrentPage();
 
   const onClickShowLatestButton = useCallback(async() => {
     if (page == null) {
@@ -21,8 +21,8 @@ export const OldRevisionAlert = (): JSX.Element => {
 
     const url = returnPathForURL(page.path, page._id);
     await router.push(url);
-    fetchAndUpdatePage();
-  }, [fetchAndUpdatePage, page, router]);
+    fetchCurrentPage();
+  }, [fetchCurrentPage, page, router]);
 
   if (page == null || isOldRevisionPage) {
     return <></>;

+ 4 - 4
apps/app/src/components/PageView/PageAlerts/TrashPageAlert.tsx

@@ -6,7 +6,7 @@ import { useRouter } from 'next/router';
 import { useTranslation } from 'react-i18next';
 
 import {
-  useCurrentPageData, useCurrentPagePath, useIsTrashPage, usePageFetcher,
+  useCurrentPageData, useCurrentPagePath, useIsTrashPage, useFetchCurrentPage,
 } from '~/states/page';
 import { usePageDeleteModal, usePutBackPageModal } from '~/stores/modal';
 import { useSWRxPageInfo } from '~/stores/page';
@@ -37,7 +37,7 @@ export const TrashPageAlert = (): JSX.Element => {
   const { open: openPutBackPageModal } = usePutBackPageModal();
   const [currentPagePath] = useCurrentPagePath();
 
-  const { fetchAndUpdatePage } = usePageFetcher();
+  const { fetchCurrentPage } = useFetchCurrentPage();
 
   const deleteUser = pageData?.deleteUser;
   const deletedAt = pageData?.deletedAt ? format(new Date(pageData?.deletedAt), 'yyyy/MM/dd HH:mm') : '';
@@ -58,7 +58,7 @@ export const TrashPageAlert = (): JSX.Element => {
         unlink(currentPagePath);
 
         router.push(`/${pageId}`);
-        fetchAndUpdatePage();
+        fetchCurrentPage();
         mutateRecentlyUpdated();
       }
       catch (err) {
@@ -67,7 +67,7 @@ export const TrashPageAlert = (): JSX.Element => {
       }
     };
     openPutBackPageModal({ pageId, path: pagePath }, { onPutBacked: putBackedHandler });
-  }, [isEmptyPage, openPutBackPageModal, pageId, pagePath, currentPagePath, router, fetchAndUpdatePage]);
+  }, [isEmptyPage, openPutBackPageModal, pageId, pagePath, currentPagePath, router, fetchCurrentPage]);
 
   const openPageDeleteModalHandler = useCallback(() => {
     // User cannot operate empty page.

+ 4 - 4
apps/app/src/components/PageView/PageAlerts/WipPageAlert.tsx

@@ -2,13 +2,13 @@ import React, { useCallback, type JSX } from 'react';
 
 import { useTranslation } from 'react-i18next';
 
-import { useCurrentPageData, usePageFetcher } from '~/states/page';
+import { useCurrentPageData, useFetchCurrentPage } from '~/states/page';
 
 
 export const WipPageAlert = (): JSX.Element => {
   const { t } = useTranslation();
   const [currentPage] = useCurrentPageData();
-  const { fetchAndUpdatePage } = usePageFetcher();
+  const { fetchCurrentPage } = useFetchCurrentPage();
 
   const clickPagePublishButton = useCallback(async() => {
     const pageId = currentPage?._id;
@@ -21,7 +21,7 @@ export const WipPageAlert = (): JSX.Element => {
       const publish = (await import('~/client/services/page-operation')).publish;
       await publish(pageId);
 
-      await fetchAndUpdatePage();
+      await fetchCurrentPage();
 
       const mutatePageTree = (await import('~/stores/page-listing')).mutatePageTree;
       await mutatePageTree();
@@ -36,7 +36,7 @@ export const WipPageAlert = (): JSX.Element => {
       const toastError = (await import('~/client/util/toastr')).toastError;
       toastError(t('wip_page.fail_publish_page'));
     }
-  }, [currentPage?._id, fetchAndUpdatePage, t]);
+  }, [currentPage?._id, fetchCurrentPage, t]);
 
 
   if (!currentPage?.wip) {

+ 0 - 1
apps/app/src/components/PageView/PageView.tsx

@@ -69,7 +69,6 @@ export const PageView = (props: Props): JSX.Element => {
   const markdown = page?.revision?.body;
   const isSlide = useSlidesByFrontmatter(markdown, rendererConfig.isEnabledMarp);
 
-
   // ***************************  Auto Scroll  ***************************
   useEffect(() => {
     // do nothing if hash is empty

+ 8 - 10
apps/app/src/pages/[[...path]].page.tsx

@@ -1,5 +1,5 @@
 import type { ReactNode, JSX } from 'react';
-import React, { useEffect } from 'react';
+import React, { useEffect, useRef } from 'react';
 
 import EventEmitter from 'events';
 
@@ -35,7 +35,7 @@ import { useEditorModeClassName } from '~/services/layout/use-editor-mode-class-
 import { useHydratePageAtoms } from '~/states/hydrate/page';
 import { useHydrateSidebarAtoms } from '~/states/hydrate/sidebar';
 import {
-  useCurrentPageData, usePageFetcher, useCurrentPageId, useCurrentPagePath,
+  useCurrentPageData, useFetchCurrentPage, useCurrentPageId, useCurrentPagePath,
 } from '~/states/page';
 import {
   useCurrentUser,
@@ -272,11 +272,11 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   // Initialize Jotai atoms with initial data
   useHydratePageAtoms(pageWithMeta?.data);
 
-  const { fetchAndUpdatePage } = usePageFetcher();
+  const { fetchCurrentPage } = useFetchCurrentPage();
   const { trigger: mutateCurrentPageYjsDataFromApi } = useSWRMUTxCurrentPageYjsData();
 
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
-  const [currentPageId] = useCurrentPageId();
+  const [currentPageId, setCurrentPageId] = useCurrentPageId();
   const [currentPagePath] = useCurrentPagePath();
 
   const { mutate: mutateCurrentPageYjsData } = useCurrentPageYjsData();
@@ -290,9 +290,10 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
       return;
     }
 
-    if (currentPageId != null && revisionId != null && !props.isNotFound) {
+    if (pageId != null && revisionId != null && !props.isNotFound) {
       const mutatePageData = async() => {
-        const pageData = await fetchAndUpdatePage();
+        setCurrentPageId(pageId);
+        const pageData = await fetchCurrentPage();
         mutateEditingMarkdown(pageData?.revision?.body);
       };
 
@@ -300,10 +301,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
       // Because pageWIthMeta does not contain revision.body
       mutatePageData();
     }
-  }, [
-    revisionId, currentPageId, fetchAndUpdatePage,
-    mutateCurrentPageYjsDataFromApi, mutateEditingMarkdown, props.isNotFound, props.skipSSR,
-  ]);
+  }, [revisionId, currentPageId, mutateEditingMarkdown, props.isNotFound, props.skipSSR, fetchCurrentPage, pageId, setCurrentPageId]);
 
   // Load current yjs data
   useEffect(() => {

+ 4 - 4
apps/app/src/pages/share/[[...path]].page.tsx

@@ -21,7 +21,7 @@ import type { IShareLinkHasId } from '~/interfaces/share-link';
 import type { PageDocument, PageModel } from '~/server/models/page';
 import ShareLink from '~/server/models/share-link';
 import { useHydrateSharedPageAtoms } from '~/states/hydrate/page';
-import { useCurrentPageData, usePageFetcher } from '~/states/page';
+import { useCurrentPageData, useFetchCurrentPage } from '~/states/page';
 import {
   useCurrentUser, useRendererConfig, useIsSearchPage, useCurrentPathname,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useIsContainerFluid, useIsEnabledMarp,
@@ -108,7 +108,7 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   useShowPageSideAuthors(props.showPageSideAuthors);
   useIsContainerFluid(props.isContainerFluid);
 
-  const { fetchAndUpdatePage } = usePageFetcher();
+  const { fetchCurrentPage } = useFetchCurrentPage();
 
   useEffect(() => {
     if (!props.skipSSR) {
@@ -116,9 +116,9 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
     }
 
     if (props.shareLink?.relatedPage._id != null && !props.isNotFound) {
-      fetchAndUpdatePage();
+      fetchCurrentPage();
     }
-  }, [fetchAndUpdatePage, props.isNotFound, props.shareLink?.relatedPage._id, props.skipSSR]);
+  }, [fetchCurrentPage, props.isNotFound, props.shareLink?.relatedPage._id, props.skipSSR]);
 
 
   const pagePath = props.shareLinkRelatedPage?.path ?? '';

+ 0 - 6
apps/app/src/states/page/hooks.ts

@@ -12,7 +12,6 @@ import {
   currentPagePathAtom,
   pageNotFoundAtom,
   latestRevisionAtom,
-  setCurrentPageAtom,
   // New atoms for enhanced functionality
   remoteRevisionIdAtom,
   remoteRevisionBodyAtom,
@@ -46,11 +45,6 @@ export const useLatestRevision = (): UseAtom<typeof latestRevisionAtom> => {
   return useAtom(latestRevisionAtom);
 };
 
-// Write hooks for updating page state
-export const useSetCurrentPage = (): ((page: IPagePopulatedToShowRevision | undefined) => void) => {
-  return useAtom(setCurrentPageAtom)[1];
-};
-
 export const useTemplateTags = (): UseAtom<typeof templateTagsAtom> => {
   return useAtom(templateTagsAtom);
 };

+ 1 - 2
apps/app/src/states/page/index.ts

@@ -12,7 +12,6 @@ export {
   useCurrentPagePath,
   usePageNotFound,
   useLatestRevision,
-  useSetCurrentPage,
   // Remote revision hooks (replacements for stores/remote-latest-page.ts)
   useRemoteRevisionId,
   useRemoteRevisionBody,
@@ -24,7 +23,7 @@ export {
 
 // Data fetching hooks
 export {
-  usePageFetcher,
+  useFetchCurrentPage,
 } from './page-fetcher';
 
 // Re-export types that external consumers might need

+ 0 - 11
apps/app/src/states/page/internal-atoms.ts

@@ -52,17 +52,6 @@ export const isRevisionOutdatedAtom = atom((get) => {
   return remoteRevisionId !== currentRevisionId;
 });
 
-// Action atoms for state updates
-export const setCurrentPageAtom = atom(
-  null,
-  (get, set, page: IPagePopulatedToShowRevision | undefined) => {
-    set(currentPageDataAtom, page);
-    if (page?._id) {
-      set(currentPageIdAtom, page._id);
-    }
-  },
-);
-
 export const setPageStatusAtom = atom(
   null,
   (get, set, status: { isNotFound?: boolean; isLatestRevision?: boolean }) => {

+ 34 - 57
apps/app/src/states/page/page-fetcher.ts

@@ -1,62 +1,46 @@
-import { useCallback } from 'react';
+import { useCallback, useState } from 'react';
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { isClient } from '@growi/core/dist/utils';
-import { useAtom } from 'jotai';
-import useSWRMutation, { type SWRMutationResponse } from 'swr/mutation';
+import { useAtomCallback } from 'jotai/utils';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { useShareLinkId } from '~/stores-universal/context';
-import type { AxiosResponse } from '~/utils/axios';
 
-import { currentPageIdAtom, setCurrentPageAtom } from './internal-atoms';
+import { currentPageIdAtom, currentPageDataAtom } from './internal-atoms';
 
-/**
- * Hybrid approach: Use Jotai for state management, SWR for data fetching
- * This eliminates the complex shouldMutate logic while keeping SWR's benefits
- */
-
-const getPageApiErrorHandler = (errs: AxiosResponse[]): IPagePopulatedToShowRevision | null => {
-  if (!Array.isArray(errs)) {
-    throw Error('error is not array');
-  }
-
-  const statusCode = errs[0].status;
-  if (statusCode === 403 || statusCode === 404) {
-    // for NotFoundPage
-    return null;
-  }
-  throw Error('failed to get page');
-};
 
 /**
  * Simplified page fetching hook using Jotai + SWR
  * Replaces the complex useSWRMUTxCurrentPage with cleaner state management
  */
-export const usePageFetcher = (): SWRMutationResponse<IPagePopulatedToShowRevision | null, Error> & {
-  fetchAndUpdatePage: () => Promise<IPagePopulatedToShowRevision | null>;
+export const useFetchCurrentPage = (): {
+  fetchCurrentPage: () => Promise<IPagePopulatedToShowRevision | null>,
+  isLoading: boolean,
+  error: Error | null,
 } => {
-  const [currentPageId] = useAtom(currentPageIdAtom);
   const { data: shareLinkId } = useShareLinkId();
-  const setCurrentPage = useAtom(setCurrentPageAtom)[1];
 
-  // Get URL parameter for specific revisionId
-  let revisionId: string | undefined;
-  if (isClient()) {
-    const urlParams = new URLSearchParams(window.location.search);
-    const requestRevisionId = urlParams.get('revisionId');
-    revisionId = requestRevisionId != null ? requestRevisionId : undefined;
-  }
+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState<Error | null>(null);
 
-  const key = 'fetchCurrentPage';
+  const fetchCurrentPage = useAtomCallback(
+    useCallback(async(get, set) => {
+      const currentPageId = get(currentPageIdAtom);
 
-  const swrMutationResult = useSWRMutation(
-    key,
-    async() => {
-      if (!currentPageId) {
-        return null;
+      if (!currentPageId) return null;
+
+      // Get URL parameter for specific revisionId
+      let revisionId: string | undefined;
+      if (isClient()) {
+        const urlParams = new URLSearchParams(window.location.search);
+        const requestRevisionId = urlParams.get('revisionId');
+        revisionId = requestRevisionId != null ? requestRevisionId : undefined;
       }
 
+      setIsLoading(true);
+      setError(null);
+
       try {
         const response = await apiv3Get<{ page: IPagePopulatedToShowRevision }>(
           '/page',
@@ -64,29 +48,22 @@ export const usePageFetcher = (): SWRMutationResponse<IPagePopulatedToShowRevisi
         );
 
         const newData = response.data.page;
-
-        // Update Jotai state instead of manual SWR cache mutation
-        setCurrentPage(newData);
+        set(currentPageDataAtom, newData);
 
         return newData;
       }
-      catch (error) {
-        return getPageApiErrorHandler([error]);
+      catch (err) {
+        // TODO: Handle error properly
+        // ref: https://redmine.weseek.co.jp/issues/169797
+        setError(new Error('Failed to fetch current page'));
+        return null;
+      }
+      finally {
+        setIsLoading(false);
       }
-    },
-    {
-      populateCache: false, // We're using Jotai for state, not SWR cache
-      revalidate: false,
-    },
+    }, [shareLinkId]),
   );
 
-  const fetchAndUpdatePage = useCallback(async() => {
-    const result = await swrMutationResult.trigger();
-    return result ?? null;
-  }, [swrMutationResult]);
+  return { fetchCurrentPage, isLoading, error };
 
-  return {
-    ...swrMutationResult,
-    fetchAndUpdatePage,
-  };
 };