Yuki Takei 8 месяцев назад
Родитель
Сommit
17284651f9
61 измененных файлов с 242 добавлено и 215 удалено
  1. 3 2
      apps/app/src/client/components/Bookmarks/BookmarkFolderTree.tsx
  2. 3 2
      apps/app/src/client/components/Bookmarks/BookmarkItem.tsx
  3. 3 2
      apps/app/src/client/components/Comments.tsx
  4. 4 4
      apps/app/src/client/components/Hotkeys/Subscribers/CreatePage.jsx
  5. 4 3
      apps/app/src/client/components/ItemsTree/ItemsTree.tsx
  6. 4 3
      apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx
  7. 2 2
      apps/app/src/client/components/Navbar/GrowiNavbarBottom.tsx
  8. 2 2
      apps/app/src/client/components/Navbar/PageEditorModeManager.tsx
  9. 2 2
      apps/app/src/client/components/Page/DisplaySwitcher.tsx
  10. 2 2
      apps/app/src/client/components/Page/PageContentsUtilities.tsx
  11. 4 4
      apps/app/src/client/components/PageAccessoriesModal/PageAttachment.tsx
  12. 3 3
      apps/app/src/client/components/PageAccessoriesModal/PageHistory.tsx
  13. 2 2
      apps/app/src/client/components/PageAccessoriesModal/ShareLink/ShareLink.tsx
  14. 2 2
      apps/app/src/client/components/PageAccessoriesModal/ShareLink/ShareLinkForm.tsx
  15. 2 2
      apps/app/src/client/components/PageComment/CommentEditor.tsx
  16. 3 2
      apps/app/src/client/components/PageControls/PageControls.tsx
  17. 5 5
      apps/app/src/client/components/PageEditor/ConflictDiffModal.tsx
  18. 3 2
      apps/app/src/client/components/PageEditor/EditorNavbarBottom/GrantSelector.tsx
  19. 3 3
      apps/app/src/client/components/PageEditor/EditorNavbarBottom/SavePageControls.tsx
  20. 2 2
      apps/app/src/client/components/PageEditor/LinkEditModal.tsx
  21. 16 9
      apps/app/src/client/components/PageEditor/PageEditor.tsx
  22. 3 3
      apps/app/src/client/components/PageEditor/PageEditorReadOnly.tsx
  23. 3 3
      apps/app/src/client/components/PageEditor/conflict.tsx
  24. 3 3
      apps/app/src/client/components/PageEditor/page-path-rename-utils.ts
  25. 2 2
      apps/app/src/client/components/PageHeader/PageHeader.tsx
  26. 2 2
      apps/app/src/client/components/PagePresentationModal.tsx
  27. 2 2
      apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx
  28. 3 3
      apps/app/src/client/components/PageStatusAlert.tsx
  29. 2 2
      apps/app/src/client/components/ReactMarkdownComponents/DrawioViewerWithEditButton.tsx
  30. 2 2
      apps/app/src/client/components/ReactMarkdownComponents/TableWithEditButton.tsx
  31. 2 2
      apps/app/src/client/components/Sidebar/PageCreateButton/PageCreateButton.tsx
  32. 4 4
      apps/app/src/client/components/Sidebar/PageCreateButton/hooks/use-create-new-page.ts
  33. 3 3
      apps/app/src/client/components/Sidebar/PageTree/PageTreeSubstance.tsx
  34. 2 2
      apps/app/src/client/components/TableOfContents.tsx
  35. 2 2
      apps/app/src/client/services/create-page/use-create-page.tsx
  36. 4 4
      apps/app/src/client/services/create-page/use-create-template-page.ts
  37. 7 6
      apps/app/src/client/services/page-operation.ts
  38. 2 2
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  39. 2 2
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  40. 5 5
      apps/app/src/client/services/side-effects/page-updated.ts
  41. 3 2
      apps/app/src/components/PageView/PageAlerts/FixPageGrantAlert.tsx
  42. 2 2
      apps/app/src/components/PageView/PageAlerts/FullTextSearchNotCoverAlert.tsx
  43. 6 6
      apps/app/src/components/PageView/PageAlerts/OldRevisionAlert.tsx
  44. 3 2
      apps/app/src/components/PageView/PageAlerts/PageStaleAlert.tsx
  45. 10 9
      apps/app/src/components/PageView/PageAlerts/TrashPageAlert.tsx
  46. 2 2
      apps/app/src/features/openai/client/services/editor-assistant/use-editor-assistant.tsx
  47. 2 2
      apps/app/src/features/page-bulk-export/client/components/PageBulkExportSelectModal.tsx
  48. 2 2
      apps/app/src/features/search/client/components/SearchMethodMenuItem.tsx
  49. 2 3
      apps/app/src/pages/_private-legacy-pages.page.tsx
  50. 2 3
      apps/app/src/pages/_search.page.tsx
  51. 2 3
      apps/app/src/pages/me/[[...path]].page.tsx
  52. 11 6
      apps/app/src/pages/share/[[...path]].page.tsx
  53. 2 3
      apps/app/src/pages/tags.page.tsx
  54. 2 3
      apps/app/src/pages/trash.page.tsx
  55. 31 2
      apps/app/src/states/hydrate/page.ts
  56. 11 25
      apps/app/src/states/page/hooks.ts
  57. 0 11
      apps/app/src/states/page/index.ts
  58. 3 3
      apps/app/src/states/page/internal-atoms.ts
  59. 2 2
      apps/app/src/stores-universal/ui.tsx
  60. 11 6
      apps/app/src/stores/remote-latest-page.ts
  61. 4 4
      apps/app/src/stores/renderer.tsx

+ 3 - 2
apps/app/src/client/components/Bookmarks/BookmarkFolderTree.tsx

@@ -9,13 +9,14 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
 
 import { toastSuccess } from '~/client/util/toastr';
 import type { OnDeletedFunction } from '~/interfaces/ui';
+import { useCurrentPageData } from '~/states/page';
 import { useIsReadOnlyUser } from '~/stores-universal/context';
 import {
   useSWRxUserBookmarks, useSWRMUTxCurrentUserBookmarks,
 } from '~/stores/bookmark';
 import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
 import { usePageDeleteModal } from '~/stores/modal';
-import { mutateAllPageInfo, useSWRMUTxPageInfo, useSWRxCurrentPage } from '~/stores/page';
+import { mutateAllPageInfo, useSWRMUTxPageInfo } from '~/stores/page';
 
 import { BookmarkFolderItem } from './BookmarkFolderItem';
 import { BookmarkItem } from './BookmarkItem';
@@ -42,7 +43,7 @@ export const BookmarkFolderTree: React.FC<Props> = (props: Props) => {
   const router = useRouter();
 
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
   const { data: bookmarkFolders, mutate: mutateBookmarkFolders } = useSWRxBookmarkFolderAndChild(userId);
   const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxUserBookmarks(userId ?? null);
   const { trigger: mutatePageInfo } = useSWRMUTxPageInfo(currentPage?._id ?? null);

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

@@ -15,8 +15,9 @@ 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 { usePutBackPageModal } from '~/stores/modal';
-import { mutateAllPageInfo, useSWRMUTxCurrentPage, useSWRxPageInfo } from '~/stores/page';
+import { mutateAllPageInfo, useSWRxPageInfo } from '~/stores/page';
 
 import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 import { PageListItemS } from '../PageList/PageListItemS';
@@ -51,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 } = useSWRMUTxCurrentPage();
+  const { trigger: mutateCurrentPage } = usePageFetcher();
 
   const paddingLeft = BASE_BOOKMARK_PADDING + (BASE_FOLDER_PADDING * (level));
   const dragItem: Partial<DragItemDataType> = {

+ 3 - 2
apps/app/src/client/components/Comments.tsx

@@ -8,9 +8,10 @@ import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import { debounce } from 'throttle-debounce';
 
+import { useIsTrashPage } from '~/states/page';
 import { useCurrentUser } from '~/stores-universal/context';
 import { useSWRxPageComment } from '~/stores/comment';
-import { useIsTrashPage, useSWRMUTxPageInfo } from '~/stores/page';
+import { useSWRMUTxPageInfo } from '~/stores/page';
 
 
 const { isTopPage } = pagePathUtils;
@@ -36,7 +37,7 @@ export const Comments = (props: CommentsProps): JSX.Element => {
 
   const { mutate } = useSWRxPageComment(pageId);
   const { trigger: mutatePageInfo } = useSWRMUTxPageInfo(pageId);
-  const { data: isDeleted } = useIsTrashPage();
+  const [isDeleted] = useIsTrashPage();
   const { data: currentUser } = useCurrentUser();
 
   const pageCommentParentRef = useRef<HTMLDivElement>(null);

+ 4 - 4
apps/app/src/client/components/Hotkeys/Subscribers/CreatePage.jsx

@@ -2,21 +2,21 @@ import React, { useEffect } from 'react';
 
 import PropTypes from 'prop-types';
 
+import { useCurrentPagePath } from '~/states/page';
 import { usePageCreateModal } from '~/stores/modal';
-import { useCurrentPagePath } from '~/stores/page';
 
 const CreatePage = React.memo((props) => {
 
   const { open: openCreateModal } = usePageCreateModal();
-  const { data: currentPath = '' } = useCurrentPagePath();
+  const [currentPath] = useCurrentPagePath();
 
   // setup effect
   useEffect(() => {
-    openCreateModal(currentPath);
+    openCreateModal(currentPath ?? '');
 
     // remove this
     props.onDeleteRender(this);
-  }, [openCreateModal, props]);
+  }, [currentPath, openCreateModal, props]);
 
   return <></>;
 });

+ 4 - 3
apps/app/src/client/components/ItemsTree/ItemsTree.tsx

@@ -12,9 +12,10 @@ 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 type { IPageForPageDuplicateModal } from '~/stores/modal';
 import { usePageDuplicateModal, usePageDeleteModal } from '~/stores/modal';
-import { mutateAllPageInfo, useCurrentPagePath, useSWRMUTxCurrentPage } from '~/stores/page';
+import { mutateAllPageInfo } from '~/stores/page';
 import {
   useSWRxRootPage, mutatePageTree, mutatePageList,
 } from '~/stores/page-listing';
@@ -54,7 +55,7 @@ export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
   const router = useRouter();
 
   const { data: rootPageResult, error } = useSWRxRootPage({ suspense: true });
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openDeleteModal } = usePageDeleteModal();
 
@@ -62,7 +63,7 @@ export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
   const { data: ptDescCountMap, update: updatePtDescCountMap } = usePageTreeDescCountMap();
 
   // for mutation
-  const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
+  const { trigger: mutateCurrentPage } = usePageFetcher();
 
   useEffect(() => {
     if (socket == null) {

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

@@ -24,6 +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 {
   useCurrentPathname,
   useCurrentUser, useIsGuestUser, useIsReadOnlyUser, useIsBulkExportPagesEnabled,
@@ -35,7 +36,7 @@ import {
   usePageDuplicateModal, usePageRenameModal, usePageDeleteModal, usePagePresentationModal,
 } from '~/stores/modal';
 import {
-  useSWRMUTxCurrentPage, useCurrentPageId, useSWRxPageInfo,
+  useSWRxPageInfo,
 } from '~/stores/page';
 import { mutatePageTree, mutateRecentlyUpdated } from '~/stores/page-listing';
 import {
@@ -254,7 +255,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const router = useRouter();
 
   const { data: shareLinkId } = useShareLinkId();
-  const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
+  const { trigger: mutateCurrentPage } = usePageFetcher();
 
   const { data: currentPathname } = useCurrentPathname();
   const isSharedPage = pagePathUtils.isSharedPage(currentPathname ?? '');
@@ -263,7 +264,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const revisionId = (revision != null && isPopulated(revision)) ? revision._id : undefined;
 
   const { data: editorMode } = useEditorMode();
-  const { data: pageId } = useCurrentPageId();
+  const [pageId] = useCurrentPageId();
   const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();

+ 2 - 2
apps/app/src/client/components/Navbar/GrowiNavbarBottom.tsx

@@ -2,10 +2,10 @@ import React, { useCallback, type JSX } from 'react';
 
 import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import { useSearchModal } from '~/features/search/client/stores/search';
+import { useCurrentPagePath } from '~/states/page';
 import { useDrawerOpened } from '~/states/ui/sidebar';
 import { useIsSearchPage } from '~/stores-universal/context';
 import { usePageCreateModal } from '~/stores/modal';
-import { useCurrentPagePath } from '~/stores/page';
 
 import styles from './GrowiNavbarBottom.module.scss';
 
@@ -14,7 +14,7 @@ export const GrowiNavbarBottom = (): JSX.Element => {
 
   const [isDrawerOpened, setIsDrawerOpened] = useDrawerOpened();
   const { open: openCreateModal } = usePageCreateModal();
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
   const { data: isSearchPage } = useIsSearchPage();
   const { open: openSearchModal } = useSearchModal();
 

+ 2 - 2
apps/app/src/client/components/Navbar/PageEditorModeManager.tsx

@@ -8,8 +8,8 @@ import { useTranslation } from 'next-i18next';
 
 import { useCreatePage } from '~/client/services/create-page';
 import { toastError } from '~/client/util/toastr';
+import { usePageNotFound } from '~/states/page';
 import { EditorMode, useEditorMode } from '~/stores-universal/ui';
-import { useIsNotFound } from '~/stores/page';
 import { useIsDeviceLargerThanMd } from '~/stores/ui';
 import { useCurrentPageYjsData } from '~/stores/yjs';
 
@@ -66,7 +66,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
 
   const { t } = useTranslation('commons');
 
-  const { data: isNotFound } = useIsNotFound();
+  const [isNotFound] = usePageNotFound();
   const { mutate: mutateEditorMode } = useEditorMode();
   const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
   const { data: currentPageYjsData } = useCurrentPageYjsData();

+ 2 - 2
apps/app/src/client/components/Page/DisplaySwitcher.tsx

@@ -3,10 +3,10 @@ import type { JSX } from 'react';
 import dynamic from 'next/dynamic';
 
 import { useHashChangedEffect } from '~/client/services/side-effects/hash-changed';
+import { useLatestRevision } from '~/states/page';
 import { useIsEditable } from '~/stores-universal/context';
 import { EditorMode, useEditorMode } from '~/stores-universal/ui';
 import { useReservedNextCaretLine } from '~/stores/editor';
-import { useIsLatestRevision } from '~/stores/page';
 
 import { LazyRenderer } from '../Common/LazyRenderer';
 
@@ -19,7 +19,7 @@ export const DisplaySwitcher = (): JSX.Element => {
 
   const { data: editorMode = EditorMode.View } = useEditorMode();
   const { data: isEditable } = useIsEditable();
-  const { data: isLatestRevision } = useIsLatestRevision();
+  const [isLatestRevision] = useLatestRevision();
 
   useHashChangedEffect();
   useReservedNextCaretLine();

+ 2 - 2
apps/app/src/client/components/Page/PageContentsUtilities.tsx

@@ -5,13 +5,13 @@ import { useDrawioModalLauncherForView } from '~/client/services/side-effects/dr
 import { useHandsontableModalLauncherForView } from '~/client/services/side-effects/handsontable-modal-launcher-for-view';
 import { toastSuccess, toastError, toastWarning } from '~/client/util/toastr';
 import { PageUpdateErrorCode } from '~/interfaces/apiv3';
-import { useCurrentPageId } from '~/stores/page';
+import { useCurrentPageId } from '~/states/page';
 
 
 export const PageContentsUtilities = (): null => {
   const { t } = useTranslation();
 
-  const { data: pageId } = useCurrentPageId();
+  const [pageId] = useCurrentPageId();
   const updateStateAfterSave = useUpdateStateAfterSave(pageId);
 
   useHandsontableModalLauncherForView({

+ 4 - 4
apps/app/src/client/components/PageAccessoriesModal/PageAttachment.tsx

@@ -5,10 +5,10 @@ import React, {
 import type { IAttachmentHasId } from '@growi/core';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 
+import { useCurrentPageData, useCurrentPageId } from '~/states/page';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/stores-universal/context';
 import { useSWRxAttachments } from '~/stores/attachment';
 import { useDeleteAttachmentModal } from '~/stores/modal';
-import { useSWRxCurrentPage, useCurrentPageId } from '~/stores/page';
 
 import { PageAttachmentList } from '../PageAttachment/PageAttachmentList';
 import PaginationWrapper from '../PaginationWrapper';
@@ -20,8 +20,9 @@ const checkIfFileInUse = (markdown: string, attachment): boolean => {
 
 const PageAttachment = (): JSX.Element => {
 
-  // Static SWRs
-  const { data: pageId } = useCurrentPageId();
+  const [pageId] = useCurrentPageId();
+  const [currentPage] = useCurrentPageData();
+
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
 
@@ -33,7 +34,6 @@ const PageAttachment = (): JSX.Element => {
   // SWRs
   const { data: dataAttachments, remove } = useSWRxAttachments(pageId, pageNumber);
   const { open: openDeleteAttachmentModal } = useDeleteAttachmentModal();
-  const { data: currentPage } = useSWRxCurrentPage();
   const markdown = currentPage?.revision?.body;
 
   // Custom hooks

+ 3 - 3
apps/app/src/client/components/PageAccessoriesModal/PageHistory.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { useCurrentPagePath, useCurrentPageId } from '~/stores/page';
+import { useCurrentPagePath, useCurrentPageId } from '~/states/page';
 import loggerFactory from '~/utils/logger';
 
 import { PageRevisionTable } from '../PageHistory/PageRevisionTable';
@@ -16,8 +16,8 @@ type PageHistoryProps = {
 export const PageHistory: React.FC<PageHistoryProps> = (props: PageHistoryProps) => {
   const { onClose } = props;
 
-  const { data: currentPageId } = useCurrentPageId();
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPageId] = useCurrentPageId();
+  const [currentPagePath] = useCurrentPagePath();
 
   const comparingRevisions = useAutoComparingRevisionsByQueryParam();
 

+ 2 - 2
apps/app/src/client/components/PageAccessoriesModal/ShareLink/ShareLink.tsx

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
 
 import { apiv3Delete } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
-import { useCurrentPageId } from '~/stores/page';
+import { useCurrentPageId } from '~/states/page';
 import { useSWRxSharelink } from '~/stores/share-link';
 
 import { ShareLinkForm } from './ShareLinkForm';
@@ -14,7 +14,7 @@ export const ShareLink = (): JSX.Element => {
   const { t } = useTranslation();
   const [isOpenShareLinkForm, setIsOpenShareLinkForm] = useState<boolean>(false);
 
-  const { data: currentPageId } = useCurrentPageId();
+  const [currentPageId] = useCurrentPageId();
 
   const { data: currentShareLinks, mutate } = useSWRxSharelink(currentPageId);
 

+ 2 - 2
apps/app/src/client/components/PageAccessoriesModal/ShareLink/ShareLinkForm.tsx

@@ -8,7 +8,7 @@ import { useTranslation } from 'next-i18next';
 
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
-import { useCurrentPageId } from '~/stores/page';
+import { useCurrentPageId } from '~/states/page';
 
 
 const ExpirationType = {
@@ -33,7 +33,7 @@ export const ShareLinkForm: FC<Props> = (props: Props) => {
   const [customExpirationDate, setCustomExpirationDate] = useState<Date>(new Date());
   const [customExpirationTime, setCustomExpirationTime] = useState<Date>(new Date());
 
-  const { data: currentPageId } = useCurrentPageId();
+  const [currentPageId] = useCurrentPageId();
 
   const handleChangeExpirationType = useCallback((expirationType: ExpirationType) => {
     setExpirationType(expirationType);

+ 2 - 2
apps/app/src/client/components/PageComment/CommentEditor.tsx

@@ -18,6 +18,7 @@ import {
 
 import { uploadAttachments } from '~/client/services/upload-attachments';
 import { toastError } from '~/client/util/toastr';
+import { useCurrentPagePath } from '~/states/page';
 import {
   useCurrentUser, useIsSlackConfigured, useAcceptedUploadFileType,
 } from '~/stores-universal/context';
@@ -26,7 +27,6 @@ import { useSWRxPageComment } from '~/stores/comment';
 import {
   useSWRxSlackChannels, useIsSlackEnabled, useIsEnabledUnsavedWarning, useEditorSettings,
 } from '~/stores/editor';
-import { useCurrentPagePath } from '~/stores/page';
 import { useCommentEditorDirtyMap } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
@@ -78,7 +78,7 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
   } = props;
 
   const { data: currentUser } = useCurrentUser();
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
   const { update: updateComment, post: postComment } = useSWRxPageComment(pageId);
   const { data: isSlackEnabled, mutate: mutateIsSlackEnabled } = useIsSlackEnabled();
   const { data: acceptedUploadFileType } = useAcceptedUploadFileType();

+ 3 - 2
apps/app/src/client/components/PageControls/PageControls.tsx

@@ -18,6 +18,7 @@ import {
 } from '~/client/services/page-operation';
 import { toastError } from '~/client/util/toastr';
 import OpenDefaultAiAssistantButton from '~/features/openai/client/components/AiAssistant/OpenDefaultAiAssistantButton';
+import { useCurrentPagePath } from '~/states/page';
 import {
   useIsGuestUser, useIsReadOnlyUser, useIsSearchPage, useIsUsersHomepageDeletionEnabled,
 } from '~/stores-universal/context';
@@ -30,7 +31,7 @@ import {
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
-import { useSWRxPageInfo, useSWRxTagsInfo, useCurrentPagePath } from '../../../stores/page';
+import { useSWRxPageInfo, useSWRxTagsInfo } from '../../../stores/page';
 import { useSWRxUsersList } from '../../../stores/user';
 import type { AdditionalMenuItemsRendererProps, ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
 import {
@@ -138,7 +139,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
   const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
   const { data: isSearchPage } = useIsSearchPage();
   const { data: isUsersHomepageDeletionEnabled } = useIsUsersHomepageDeletionEnabled();
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
 
   const isUsersHomepage = currentPagePath == null ? false : pagePathUtils.isUsersHomepage(currentPagePath);
 

+ 5 - 5
apps/app/src/client/components/PageEditor/ConflictDiffModal.tsx

@@ -14,11 +14,11 @@ import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 
+import { useCurrentPageData, useRemoteRevisionBody, useRemoteRevisionId } from '~/states/page';
 import { useCurrentUser } from '~/stores-universal/context';
 import { useConflictDiffModal } from '~/stores/modal';
-import { useSWRxCurrentPage } from '~/stores/page';
 import {
-  useRemoteRevisionBody, useRemoteRevisionId, useRemoteRevisionLastUpdatedAt, useRemoteRevisionLastUpdateUser,
+  useRemoteRevisionLastUpdatedAt, useRemoteRevisionLastUpdateUser,
 } from '~/stores/remote-latest-page';
 
 import styles from './ConflictDiffModal.module.scss';
@@ -187,12 +187,12 @@ const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element =
 
 export const ConflictDiffModal = (): JSX.Element => {
   const { data: currentUser } = useCurrentUser();
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
   const { data: conflictDiffModalStatus } = useConflictDiffModal();
 
   // state for latest page
-  const { data: remoteRevisionId } = useRemoteRevisionId();
-  const { data: remoteRevisionBody } = useRemoteRevisionBody();
+  const [remoteRevisionId] = useRemoteRevisionId();
+  const [remoteRevisionBody] = useRemoteRevisionBody();
   const { data: remoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
   const { data: remoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
 

+ 3 - 2
apps/app/src/client/components/PageEditor/EditorNavbarBottom/GrantSelector.tsx

@@ -16,8 +16,9 @@ import {
 
 import type { UserRelatedGroupsData } from '~/interfaces/page';
 import { UserGroupPageGrantStatus } from '~/interfaces/page';
+import { useCurrentPageId } from '~/states/page';
 import { useCurrentUser } from '~/stores-universal/context';
-import { useCurrentPageId, useSWRxCurrentGrantData } from '~/stores/page';
+import { useSWRxCurrentGrantData } from '~/stores/page';
 import { useSelectedGrant } from '~/stores/ui';
 
 
@@ -65,7 +66,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
 
   const shouldFetch = isSelectGroupModalShown;
   const { data: selectedGrant, mutate: mutateSelectedGrant } = useSelectedGrant();
-  const { data: currentPageId } = useCurrentPageId();
+  const [currentPageId] = useCurrentPageId();
   const { data: grantData } = useSWRxCurrentGrantData(currentPageId);
 
   const currentPageGrantData = grantData?.grantData.currentPageGrant;

+ 3 - 3
apps/app/src/client/components/PageEditor/EditorNavbarBottom/SavePageControls.tsx

@@ -13,13 +13,13 @@ import {
   DropdownToggle, DropdownMenu, DropdownItem, Modal,
 } from 'reactstrap';
 
+import { useCurrentPageData, useCurrentPagePath } from '~/states/page';
 import {
   useIsEditable, useIsAclEnabled,
   useIsSlackConfigured,
 } from '~/stores-universal/context';
 import { useEditorMode } from '~/stores-universal/ui';
 import { useWaitingSaveProcessing, useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
-import { useSWRxCurrentPage, useCurrentPagePath } from '~/stores/page';
 import { useIsDeviceLargerThanMd, useSelectedGrant } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
@@ -145,12 +145,12 @@ const SavePageButton = (props: {slackChannels: string, isSlackEnabled?: boolean,
 
 export const SavePageControls = (): JSX.Element | null => {
   const { t } = useTranslation('commons');
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
   const { data: isEditable } = useIsEditable();
   const { data: isAclEnabled } = useIsAclEnabled();
 
   const { data: editorMode } = useEditorMode();
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
   const { data: isSlackConfigured } = useIsSlackConfigured();
   const { data: isSlackEnabled, mutate: mutateIsSlackEnabled } = useIsSlackEnabled();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);

+ 2 - 2
apps/app/src/client/components/PageEditor/LinkEditModal.tsx

@@ -18,7 +18,7 @@ import {
 import validator from 'validator';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
-import { useCurrentPagePath } from '~/stores/page';
+import { useCurrentPagePath } from '~/states/page';
 import { usePreviewOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 
@@ -34,7 +34,7 @@ const logger = loggerFactory('growi:components:LinkEditModal');
 
 export const LinkEditModal = (): JSX.Element => {
   const { t } = useTranslation();
-  const { data: currentPath } = useCurrentPagePath();
+  const [currentPath] = useCurrentPagePath();
   const { data: rendererOptions } = usePreviewOptions();
   const { data: linkEditModalStatus, close } = useLinkEditModal();
 

+ 16 - 9
apps/app/src/client/components/PageEditor/PageEditor.tsx

@@ -23,6 +23,12 @@ import { useUpdatePage, extractRemoteRevisionDataFromErrorObj } from '~/client/s
 import { uploadAttachments } from '~/client/services/upload-attachments';
 import { toastError, toastSuccess, toastWarning } from '~/client/util/toastr';
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
+import {
+  useCurrentPagePath,
+  useCurrentPageData,
+  useCurrentPageId,
+  usePageNotFound,
+} from '~/states/page';
 import {
   useDefaultIndentSize, useCurrentUser,
   useCurrentPathname, useIsEnabledAttachTitleHeader,
@@ -39,7 +45,7 @@ import {
   useWaitingSaveProcessing,
 } from '~/stores/editor';
 import {
-  useCurrentPagePath, useSWRxCurrentPage, useCurrentPageId, useIsNotFound, useTemplateBodyData, useSWRxCurrentGrantData,
+  useSWRxCurrentGrantData,
 } from '~/stores/page';
 import { mutatePageTree, mutateRecentlyUpdated } from '~/stores/page-listing';
 import { usePreviewOptions } from '~/stores/renderer';
@@ -54,6 +60,7 @@ import { useScrollSync } from './ScrollSyncHelper';
 import { useConflictResolver, useConflictEffect, type ConflictHandler } from './conflict';
 
 import '@growi/editor/dist/style.css';
+import { useTemplateBody } from '~/states/page/hooks';
 
 
 const logger = loggerFactory('growi:PageEditor');
@@ -88,15 +95,15 @@ export const PageEditorSubstance = (props: Props): JSX.Element => {
   const previewRef = useRef<HTMLDivElement>(null);
   const [previewRect] = useRect(previewRef);
 
-  const { data: isNotFound } = useIsNotFound();
-  const { data: pageId } = useCurrentPageId();
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [isNotFound] = usePageNotFound();
+  const [pageId] = useCurrentPageId();
+  const [currentPagePath] = useCurrentPagePath();
   const { data: currentPathname } = useCurrentPathname();
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
   const { data: selectedGrant } = useSelectedGrant();
   const { data: editingMarkdown } = useEditingMarkdown();
   const { data: isEnabledAttachTitleHeader } = useIsEnabledAttachTitleHeader();
-  const { data: templateBodyData } = useTemplateBodyData();
+  const [templateBody] = useTemplateBody();
   const { data: isEditable } = useIsEditable();
   const { mutate: mutateWaitingSaveProcessing } = useWaitingSaveProcessing();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
@@ -141,13 +148,13 @@ export const PageEditorSubstance = (props: Props): JSX.Element => {
       const pageTitle = nodePath.basename(currentPathname);
       initialValue += `${pathUtils.attachTitleHeader(pageTitle)}\n`;
     }
-    if (templateBodyData != null) {
-      initialValue += `${templateBodyData}\n`;
+    if (templateBody != null) {
+      initialValue += `${templateBody}\n`;
     }
 
     return initialValue;
 
-  }, [isNotFound, currentPathname, editingMarkdown, isEnabledAttachTitleHeader, templateBodyData]);
+  }, [isNotFound, currentPathname, editingMarkdown, isEnabledAttachTitleHeader, templateBody]);
 
   useEffect(() => {
     // set to ref

+ 3 - 3
apps/app/src/client/components/PageEditor/PageEditorReadOnly.tsx

@@ -5,7 +5,7 @@ import { CodeMirrorEditorReadOnly } from '@growi/editor/dist/client/components/C
 import { throttle } from 'throttle-debounce';
 
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
-import { useSWRxCurrentPage, useIsLatestRevision } from '~/stores/page';
+import { useCurrentPageData, useLatestRevision } from '~/states/page';
 import { usePreviewOptions } from '~/stores/renderer';
 
 import { EditorNavbar } from './EditorNavbar';
@@ -19,9 +19,9 @@ type Props = {
 export const PageEditorReadOnly = react.memo(({ visibility }: Props): JSX.Element => {
   const previewRef = useRef<HTMLDivElement>(null);
 
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
   const { data: rendererOptions } = usePreviewOptions();
-  const { data: isLatestRevision } = useIsLatestRevision();
+  const [isLatestRevision] = useLatestRevision();
   const shouldExpandContent = useShouldExpandContent(currentPage);
 
   const { scrollEditorHandler, scrollPreviewHandler } = useScrollSync(GlobalCodeMirrorEditorKey.READONLY, previewRef);

+ 3 - 3
apps/app/src/client/components/PageEditor/conflict.tsx

@@ -10,10 +10,10 @@ import type { Save, SaveOptions } from '~/client/components/PageEditor/PageEdito
 import { useUpdateStateAfterSave } from '~/client/services/page-operation';
 import { toastSuccess } from '~/client/util/toastr';
 import { SocketEventName } from '~/interfaces/websocket';
+import { useCurrentPageData, useCurrentPageId } from '~/states/page';
 import { EditorMode, useEditorMode } from '~/stores-universal/ui';
 import { usePageStatusAlert } from '~/stores/alert';
 import { useConflictDiffModal } from '~/stores/modal';
-import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 
 
@@ -34,7 +34,7 @@ type GenerateResolveConflicthandler = () => (
 const useGenerateResolveConflictHandler: GenerateResolveConflicthandler = () => {
   const { t } = useTranslation();
 
-  const { data: pageId } = useCurrentPageId();
+  const [pageId] = useCurrentPageId();
   const { close: closePageStatusAlert } = usePageStatusAlert();
   const { close: closeConflictDiffModal } = useConflictDiffModal();
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
@@ -80,7 +80,7 @@ export const useConflictResolver: ConflictResolver = () => {
 };
 
 export const useConflictEffect = (): void => {
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
   const { close: closePageStatusAlert } = usePageStatusAlert();
   const { close: closeConflictDiffModal } = useConflictDiffModal();
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);

+ 3 - 3
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 { useSWRMUTxCurrentPage } from '~/stores/page';
+import { usePageFetcher } 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 { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
+  const { fetchAndUpdatePage } = usePageFetcher();
   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) {
-        mutateCurrentPage();
+        fetchAndUpdatePage();
       }
     };
 

+ 2 - 2
apps/app/src/client/components/PageHeader/PageHeader.tsx

@@ -2,7 +2,7 @@ import {
   useCallback, useEffect, useRef, useState, type JSX,
 } from 'react';
 
-import { useSWRxCurrentPage } from '~/stores/page';
+import { useCurrentPageData } from '~/states/page';
 import { usePageControlsX } from '~/stores/ui';
 
 import { PagePathHeader } from './PagePathHeader';
@@ -14,7 +14,7 @@ const moduleClass = styles['page-header'] ?? '';
 
 export const PageHeader = (): JSX.Element => {
 
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
   const { data: pageControlsX } = usePageControlsX();
   const pageHeaderRef = useRef<HTMLDivElement>(null);
 

+ 2 - 2
apps/app/src/client/components/PagePresentationModal.tsx

@@ -10,10 +10,10 @@ import {
   Modal, ModalBody,
 } from 'reactstrap';
 
+import { useCurrentPageData } from '~/states/page';
 import { useIsEnabledMarp } from '~/stores-universal/context';
 import { useNextThemes } from '~/stores-universal/use-next-themes';
 import { usePagePresentationModal } from '~/stores/modal';
-import { useSWRxCurrentPage } from '~/stores/page';
 import { usePresentationViewOptions } from '~/stores/renderer';
 
 import { RendererErrorMessage } from './Common/RendererErrorMessage';
@@ -38,7 +38,7 @@ const PagePresentationModal = (): JSX.Element => {
   const { isDarkMode } = useNextThemes();
   const fullscreen = useFullScreen();
 
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
   const { data: rendererOptions, isLoading } = usePresentationViewOptions();
 
   const { data: isEnabledMarp } = useIsEnabledMarp();

+ 2 - 2
apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx

@@ -13,9 +13,9 @@ import {
 import SimpleBar from 'simplebar-react';
 
 import type { IPageForItem } from '~/interfaces/page';
+import { useCurrentPageData } from '~/states/page';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/stores-universal/context';
 import { usePageSelectModal } from '~/stores/modal';
-import { useSWRxCurrentPage } from '~/stores/page';
 
 import { ItemsTree } from '../ItemsTree';
 import ItemsTreeContentSkeleton from '../ItemsTree/ItemsTreeContentSkeleton';
@@ -35,7 +35,7 @@ const PageSelectModalSubstance: FC = () => {
 
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
   const { data: pageSelectModalData } = usePageSelectModal();
 
   const isHierarchicalSelectionMode = pageSelectModalData?.opts?.isHierarchicalSelectionMode ?? false;

+ 3 - 3
apps/app/src/client/components/PageStatusAlert.tsx

@@ -2,11 +2,11 @@ import React, { useCallback, type JSX } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-import { useCurrentPageData } from '~/states/page';
+import { useCurrentPageData, useRemoteRevisionId } from '~/states/page';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/stores-universal/context';
 import { useEditorMode } from '~/stores-universal/ui';
 import { usePageStatusAlert } from '~/stores/alert';
-import { useRemoteRevisionId, useRemoteRevisionLastUpdateUser } from '~/stores/remote-latest-page';
+import { useRemoteRevisionLastUpdateUser } from '~/stores/remote-latest-page';
 
 import { Username } from '../../components/User/Username';
 
@@ -19,7 +19,7 @@ export const PageStatusAlert = (): JSX.Element => {
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
   const { data: pageStatusAlertData } = usePageStatusAlert();
-  const { data: remoteRevisionId } = useRemoteRevisionId();
+  const [remoteRevisionId] = useRemoteRevisionId();
   const { data: remoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
   const [pageData] = useCurrentPageData();
 

+ 2 - 2
apps/app/src/client/components/ReactMarkdownComponents/DrawioViewerWithEditButton.tsx

@@ -9,10 +9,10 @@ import {
 } from '@growi/remark-drawio';
 import { useTranslation } from 'next-i18next';
 
+import { useIsRevisionOutdated } from '~/states/page';
 import {
   useIsGuestUser, useIsReadOnlyUser, useIsSharedUser, useShareLinkId,
 } from '~/stores-universal/context';
-import { useIsRevisionOutdated } from '~/stores/page';
 import { useCurrentPageYjsData } from '~/stores/yjs';
 
 import '@growi/remark-drawio/dist/style.css';
@@ -34,7 +34,7 @@ export const DrawioViewerWithEditButton = React.memo((props: DrawioViewerProps):
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: shareLinkId } = useShareLinkId();
-  const { data: isRevisionOutdated } = useIsRevisionOutdated();
+  const [isRevisionOutdated] = useIsRevisionOutdated();
   const { data: currentPageYjsData } = useCurrentPageYjsData();
 
   const [isRendered, setRendered] = useState(false);

+ 2 - 2
apps/app/src/client/components/ReactMarkdownComponents/TableWithEditButton.tsx

@@ -4,10 +4,10 @@ import type EventEmitter from 'events';
 
 import type { Element } from 'hast';
 
+import { useIsRevisionOutdated } from '~/states/page';
 import {
   useIsGuestUser, useIsReadOnlyUser, useIsSharedUser, useShareLinkId,
 } from '~/stores-universal/context';
-import { useIsRevisionOutdated } from '~/stores/page';
 import { useCurrentPageYjsData } from '~/stores/yjs';
 
 import styles from './TableWithEditButton.module.scss';
@@ -30,7 +30,7 @@ const TableWithEditButtonNoMemorized = (props: TableWithEditButtonProps): JSX.El
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: shareLinkId } = useShareLinkId();
-  const { data: isRevisionOutdated } = useIsRevisionOutdated();
+  const [isRevisionOutdated] = useIsRevisionOutdated();
   const { data: currentPageYjsData } = useCurrentPageYjsData();
 
   const bol = node.position?.start.line;

+ 2 - 2
apps/app/src/client/components/Sidebar/PageCreateButton/PageCreateButton.tsx

@@ -4,8 +4,8 @@ import { Dropdown } from 'reactstrap';
 
 import { useCreateTemplatePage } from '~/client/services/create-page';
 import { useToastrOnError } from '~/client/services/use-toastr-on-error';
+import { useCurrentPagePath } from '~/states/page';
 import { usePageCreateModal } from '~/stores/modal';
-import { useCurrentPagePath } from '~/stores/page';
 
 import { CreateButton } from './CreateButton';
 import { DropendMenu } from './DropendMenu';
@@ -19,7 +19,7 @@ export const PageCreateButton = React.memo((): JSX.Element => {
   const [dropdownOpen, setDropdownOpen] = useState(false);
 
   const { open: openPageCreateModal } = usePageCreateModal();
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
 
   const { createNewPage, isCreating: isNewPageCreating } = useCreateNewPage();
   // TODO: https://redmine.weseek.co.jp/issues/138806

+ 4 - 4
apps/app/src/client/components/Sidebar/PageCreateButton/hooks/use-create-new-page.ts

@@ -3,7 +3,7 @@ import { useCallback } from 'react';
 import { Origin } from '@growi/core';
 
 import { useCreatePage } from '~/client/services/create-page';
-import { useCurrentPagePath } from '~/stores/page';
+import { useCurrentPagePath } from '~/states/page';
 
 
 type UseCreateNewPage = () => {
@@ -12,12 +12,12 @@ type UseCreateNewPage = () => {
 }
 
 export const useCreateNewPage: UseCreateNewPage = () => {
-  const { data: currentPagePath, isLoading: isLoadingPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
 
   const { isCreating, create } = useCreatePage();
 
   const createNewPage = useCallback(async() => {
-    if (isLoadingPagePath) return;
+    if (currentPagePath == null) return;
 
     return create(
       {
@@ -30,7 +30,7 @@ export const useCreateNewPage: UseCreateNewPage = () => {
         skipPageExistenceCheck: true,
       },
     );
-  }, [create, currentPagePath, isLoadingPagePath]);
+  }, [create, currentPagePath]);
 
   return {
     isCreating,

+ 3 - 3
apps/app/src/client/components/Sidebar/PageTree/PageTreeSubstance.tsx

@@ -5,8 +5,8 @@ import React, {
 import { useTranslation } from 'next-i18next';
 import { debounce } from 'throttle-debounce';
 
+import { useCurrentPageId, useCurrentPagePath } from '~/states/page';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/stores-universal/context';
-import { useCurrentPagePath, useCurrentPageId } from '~/stores/page';
 import {
   mutatePageTree, mutateRecentlyUpdated, useSWRxRootPage, useSWRxV5MigrationStatus,
 } from '~/stores/page-listing';
@@ -97,8 +97,8 @@ export const PageTreeContent = memo(({ isWipPageShown }: PageTreeContentProps) =
 
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
-  const { data: currentPath } = useCurrentPagePath();
-  const { data: targetId } = useCurrentPageId();
+  const [currentPath] = useCurrentPagePath();
+  const [targetId] = useCurrentPageId();
 
   const { data: migrationStatus } = useSWRxV5MigrationStatus({ suspense: true });
 

+ 2 - 2
apps/app/src/client/components/TableOfContents.tsx

@@ -3,7 +3,7 @@ import React, { useCallback, type JSX } from 'react';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import ReactMarkdown from 'react-markdown';
 
-import { useCurrentPagePath } from '~/stores/page';
+import { useCurrentPagePath } from '~/states/page';
 import { useTocOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 
@@ -21,7 +21,7 @@ type Props = {
 }
 
 const TableOfContents = ({ tagsElementHeight }: Props): JSX.Element => {
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
 
   const isUsersHomePage = currentPagePath != null && _isUsersHomepage(currentPagePath);
 

+ 2 - 2
apps/app/src/client/services/create-page/use-create-page.tsx

@@ -6,9 +6,9 @@ import { useTranslation } from 'react-i18next';
 import { exist, getIsNonUserRelatedGroupsGranted } from '~/client/services/page-operation';
 import { toastWarning } from '~/client/util/toastr';
 import type { IApiv3PageCreateParams } from '~/interfaces/apiv3';
+import { useCurrentPagePath } from '~/states/page';
 import { EditorMode, useEditorMode } from '~/stores-universal/ui';
 import { useGrantedGroupsInheritanceSelectModal } from '~/stores/modal';
-import { useCurrentPagePath } from '~/stores/page';
 import { useIsUntitledPage } from '~/stores/ui';
 
 import { createPage } from './create-page';
@@ -50,7 +50,7 @@ export const useCreatePage: UseCreatePage = () => {
   const router = useRouter();
   const { t } = useTranslation();
 
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
   const { mutate: mutateEditorMode } = useEditorMode();
   const { mutate: mutateIsUntitledPage } = useIsUntitledPage();
   const { open: openGrantedGroupsInheritanceSelectModal, close: closeGrantedGroupsInheritanceSelectModal } = useGrantedGroupsInheritanceSelectModal();

+ 4 - 4
apps/app/src/client/services/create-page/use-create-template-page.ts

@@ -5,7 +5,7 @@ import { isCreatablePage } from '@growi/core/dist/utils/page-path-utils';
 import { normalizePath } from '@growi/core/dist/utils/path-utils';
 
 import type { LabelType } from '~/interfaces/template';
-import { useCurrentPagePath } from '~/stores/page';
+import { useCurrentPagePath } from '~/states/page';
 
 
 import { useCreatePage } from './use-create-page';
@@ -18,20 +18,20 @@ type UseCreateTemplatePage = () => {
 
 export const useCreateTemplatePage: UseCreateTemplatePage = () => {
 
-  const { data: currentPagePath, isLoading: isLoadingPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
 
   const { isCreating, create } = useCreatePage();
   const isCreatable = currentPagePath != null && isCreatablePage(normalizePath(`${currentPagePath}/_template`));
 
   const createTemplate = useCallback(async(label: LabelType) => {
-    if (isLoadingPagePath || !isCreatable) return;
+    if (currentPagePath == null || !isCreatable) return;
 
     return create(
       {
         path: normalizePath(`${currentPagePath}/${label}`), parentPath: currentPagePath, wip: false, origin: Origin.View,
       },
     );
-  }, [currentPagePath, isCreatable, isLoadingPagePath, create]);
+  }, [currentPagePath, isCreatable, create]);
 
   return {
     isCreatable,

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

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

+ 2 - 2
apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts

@@ -7,9 +7,9 @@ import type { DrawioEditByViewerProps } from '@growi/remark-drawio';
 
 import { replaceDrawioInMarkdown } from '~/client/components/Page/markdown-drawio-util-for-view';
 import { extractRemoteRevisionDataFromErrorObj, useUpdatePage } from '~/client/services/update-page';
+import { useCurrentPageData } from '~/states/page';
 import { useShareLinkId } from '~/stores-universal/context';
 import { useConflictDiffModal, useDrawioModal } from '~/stores/modal';
-import { useSWRxCurrentPage } from '~/stores/page';
 import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 
@@ -30,7 +30,7 @@ export const useDrawioModalLauncherForView = (opts?: {
 
   const { data: shareLinkId } = useShareLinkId();
 
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
 
   const { open: openDrawioModal } = useDrawioModal();
 

+ 2 - 2
apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts

@@ -7,9 +7,9 @@ import type { MarkdownTable } from '@growi/editor';
 
 import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/client/components/Page/markdown-table-util-for-view';
 import { extractRemoteRevisionDataFromErrorObj, useUpdatePage } from '~/client/services/update-page';
+import { useCurrentPageData } from '~/states/page';
 import { useShareLinkId } from '~/stores-universal/context';
 import { useHandsontableModal, useConflictDiffModal } from '~/stores/modal';
-import { useSWRxCurrentPage } from '~/stores/page';
 import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 
@@ -30,7 +30,7 @@ export const useHandsontableModalLauncherForView = (opts?: {
 
   const { data: shareLinkId } = useShareLinkId();
 
-  const { data: currentPage } = useSWRxCurrentPage();
+  const [currentPage] = useCurrentPageData();
 
   const { open: openHandsontableModal } = useHandsontableModal();
 

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

@@ -3,9 +3,9 @@ import { useCallback, useEffect } from 'react';
 import { useGlobalSocket } from '@growi/core/dist/swr';
 
 import { SocketEventName } from '~/interfaces/websocket';
+import { useCurrentPageData, usePageFetcher } from '~/states/page';
 import { useEditorMode, EditorMode } from '~/stores-universal/ui';
 import { usePageStatusAlert } from '~/stores/alert';
-import { useSWRxCurrentPage, useSWRMUTxCurrentPage } from '~/stores/page';
 import { useSetRemoteLatestPageData, type RemoteRevisionData } from '~/stores/remote-latest-page';
 
 
@@ -15,8 +15,8 @@ export const usePageUpdatedEffect = (): void => {
 
   const { data: socket } = useGlobalSocket();
   const { data: editorMode } = useEditorMode();
-  const { data: currentPage } = useSWRxCurrentPage();
-  const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
+  const [currentPage] = useCurrentPageData();
+  const { fetchAndUpdatePage } = usePageFetcher();
   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: mutateCurrentPage });
+        openPageStatusAlert({ hideEditorMode: EditorMode.Editor, onRefleshPage: fetchAndUpdatePage });
       }
 
       // Clear cache
@@ -48,7 +48,7 @@ export const usePageUpdatedEffect = (): void => {
         closePageStatusAlert();
       }
     }
-  }, [currentPage?._id, currentPage?.revision?._id, editorMode, mutateCurrentPage, openPageStatusAlert, closePageStatusAlert, setRemoteLatestPageData]);
+  }, [currentPage?._id, currentPage?.revision?._id, editorMode, fetchAndUpdatePage, openPageStatusAlert, closePageStatusAlert, setRemoteLatestPageData]);
 
   // listen socket for someone updating this page
   useEffect(() => {

+ 3 - 2
apps/app/src/components/PageView/PageAlerts/FixPageGrantAlert.tsx

@@ -10,8 +10,9 @@ import {
 
 import { UserGroupPageGrantStatus, type IPageGrantData } from '~/interfaces/page';
 import type { PopulatedGrantedGroup, IRecordApplicableGrant, IResGrantData } from '~/interfaces/page-grant';
+import { useCurrentPageData } from '~/states/page';
 import { useCurrentUser } from '~/stores-universal/context';
-import { useSWRxApplicableGrant, useSWRxCurrentGrantData, useSWRxCurrentPage } from '~/stores/page';
+import { useSWRxApplicableGrant, useSWRxCurrentGrantData } from '~/stores/page';
 
 type ModalProps = {
   isOpen: boolean
@@ -284,7 +285,7 @@ export const FixPageGrantAlert = (): JSX.Element => {
   const { t } = useTranslation();
 
   const { data: currentUser } = useCurrentUser();
-  const { data: pageData } = useSWRxCurrentPage();
+  const [pageData] = useCurrentPageData();
   const hasParent = pageData != null ? pageData.parent != null : false;
   const pageId = pageData?._id;
 

+ 2 - 2
apps/app/src/components/PageView/PageAlerts/FullTextSearchNotCoverAlert.tsx

@@ -2,15 +2,15 @@ import type { JSX } from 'react';
 
 import { useTranslation } from 'react-i18next';
 
+import { useCurrentPageData } from '~/states/page';
 import { useElasticsearchMaxBodyLengthToIndex } from '~/stores-universal/context';
-import { useSWRxCurrentPage } from '~/stores/page';
 
 
 export const FullTextSearchNotCoverAlert = (): JSX.Element => {
   const { t } = useTranslation();
 
   const { data: elasticsearchMaxBodyLengthToIndex } = useElasticsearchMaxBodyLengthToIndex();
-  const { data } = useSWRxCurrentPage();
+  const [data] = useCurrentPageData();
 
   const markdownLength = data?.revision?.body?.length;
 

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

@@ -4,15 +4,15 @@ import { returnPathForURL } from '@growi/core/dist/utils/path-utils';
 import { useRouter } from 'next/router';
 import { useTranslation } from 'react-i18next';
 
-import { useSWRxCurrentPage, useSWRMUTxCurrentPage, useIsLatestRevision } from '~/stores/page';
+import { useCurrentPageData, useLatestRevision, usePageFetcher } from '~/states/page';
 
 export const OldRevisionAlert = (): JSX.Element => {
   const router = useRouter();
   const { t } = useTranslation();
 
-  const { data: isOldRevisionPage } = useIsLatestRevision();
-  const { data: page } = useSWRxCurrentPage();
-  const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
+  const [isOldRevisionPage] = useLatestRevision();
+  const [page] = useCurrentPageData();
+  const { fetchAndUpdatePage } = usePageFetcher();
 
   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);
-    mutateCurrentPage();
-  }, [mutateCurrentPage, page, router]);
+    fetchAndUpdatePage();
+  }, [fetchAndUpdatePage, page, router]);
 
   if (page == null || isOldRevisionPage) {
     return <></>;

+ 3 - 2
apps/app/src/components/PageView/PageAlerts/PageStaleAlert.tsx

@@ -4,8 +4,9 @@ import { isIPageInfoForEntity } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 
 
+import { useCurrentPageData } from '~/states/page';
 import { useIsEnabledStaleNotification } from '~/stores-universal/context';
-import { useSWRxCurrentPage, useSWRxPageInfo } from '~/stores/page';
+import { useSWRxPageInfo } from '~/stores/page';
 
 
 export const PageStaleAlert = ():JSX.Element => {
@@ -13,7 +14,7 @@ export const PageStaleAlert = ():JSX.Element => {
   const { data: isEnabledStaleNotification } = useIsEnabledStaleNotification();
 
   // Todo: determine if it should fetch or not like useSWRxPageInfo below after https://redmine.weseek.co.jp/issues/96788
-  const { data: pageData } = useSWRxCurrentPage();
+  const [pageData] = useCurrentPageData();
   const { data: pageInfo } = useSWRxPageInfo(isEnabledStaleNotification ? pageData?._id : null);
 
   const contentAge = isIPageInfoForEntity(pageInfo) ? pageInfo.contentAge : null;

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

@@ -5,10 +5,11 @@ import { format } from 'date-fns/format';
 import { useRouter } from 'next/router';
 import { useTranslation } from 'react-i18next';
 
-import { usePageDeleteModal, usePutBackPageModal } from '~/stores/modal';
 import {
-  useCurrentPagePath, useSWRxPageInfo, useSWRxCurrentPage, useIsTrashPage, useSWRMUTxCurrentPage,
-} from '~/stores/page';
+  useCurrentPageData, useCurrentPagePath, useIsTrashPage, usePageFetcher,
+} from '~/states/page';
+import { usePageDeleteModal, usePutBackPageModal } from '~/stores/modal';
+import { useSWRxPageInfo } from '~/stores/page';
 import { mutateRecentlyUpdated } from '~/stores/page-listing';
 import { useIsAbleToShowTrashPageManagementButtons } from '~/stores/ui';
 
@@ -26,17 +27,17 @@ export const TrashPageAlert = (): JSX.Element => {
   const router = useRouter();
 
   const { data: isAbleToShowTrashPageManagementButtons } = useIsAbleToShowTrashPageManagementButtons();
-  const { data: pageData } = useSWRxCurrentPage();
-  const { data: isTrashPage } = useIsTrashPage();
+  const [pageData] = useCurrentPageData();
+  const [isTrashPage] = useIsTrashPage();
   const pageId = pageData?._id;
   const pagePath = pageData?.path;
   const { data: pageInfo } = useSWRxPageInfo(pageId ?? null);
 
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openPutBackPageModal } = usePutBackPageModal();
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
 
-  const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
+  const { fetchAndUpdatePage } = usePageFetcher();
 
   const deleteUser = pageData?.deleteUser;
   const deletedAt = pageData?.deletedAt ? format(new Date(pageData?.deletedAt), 'yyyy/MM/dd HH:mm') : '';
@@ -57,7 +58,7 @@ export const TrashPageAlert = (): JSX.Element => {
         unlink(currentPagePath);
 
         router.push(`/${pageId}`);
-        mutateCurrentPage();
+        fetchAndUpdatePage();
         mutateRecentlyUpdated();
       }
       catch (err) {
@@ -66,7 +67,7 @@ export const TrashPageAlert = (): JSX.Element => {
       }
     };
     openPutBackPageModal({ pageId, path: pagePath }, { onPutBacked: putBackedHandler });
-  }, [currentPagePath, mutateCurrentPage, openPutBackPageModal, pageId, pagePath, router, isEmptyPage]);
+  }, [isEmptyPage, openPutBackPageModal, pageId, pagePath, currentPagePath, router, fetchAndUpdatePage]);
 
   const openPageDeleteModalHandler = useCallback(() => {
     // User cannot operate empty page.

+ 2 - 2
apps/app/src/features/openai/client/services/editor-assistant/use-editor-assistant.tsx

@@ -13,8 +13,8 @@ import { useTranslation } from 'react-i18next';
 import { type Text as YText } from 'yjs';
 
 import { apiv3Post } from '~/client/util/apiv3-client';
+import { useCurrentPageId } from '~/states/page';
 import { useIsEnableUnifiedMergeView } from '~/stores-universal/context';
-import { useCurrentPageId } from '~/stores/page';
 
 import type { AiAssistantHasId } from '../../../interfaces/ai-assistant';
 import {
@@ -139,7 +139,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
 
   // Hooks
   const { t } = useTranslation();
-  const { data: currentPageId } = useCurrentPageId();
+  const [currentPageId] = useCurrentPageId();
   const { data: isEnableUnifiedMergeView, mutate: mutateIsEnableUnifiedMergeView } = useIsEnableUnifiedMergeView();
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
   const yDocs = useSecondaryYdocs(isEnableUnifiedMergeView ?? false, { pageId: currentPageId ?? undefined, useSecondary: isEnableUnifiedMergeView ?? false });

+ 2 - 2
apps/app/src/features/page-bulk-export/client/components/PageBulkExportSelectModal.tsx

@@ -8,13 +8,13 @@ import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { usePageBulkExportSelectModal } from '~/features/page-bulk-export/client/stores/modal';
 import { PageBulkExportFormat } from '~/features/page-bulk-export/interfaces/page-bulk-export';
+import { useCurrentPagePath } from '~/states/page';
 import { useIsPdfBulkExportEnabled } from '~/stores-universal/context';
-import { useCurrentPagePath } from '~/stores/page';
 
 const PageBulkExportSelectModal = (): JSX.Element => {
   const { t } = useTranslation();
   const { data: status, close } = usePageBulkExportSelectModal();
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
   const { data: isPdfBulkExportEnabled } = useIsPdfBulkExportEnabled();
 
   const [isRestartModalOpened, setIsRestartModalOpened] = useState(false);

+ 2 - 2
apps/app/src/features/search/client/components/SearchMethodMenuItem.tsx

@@ -3,7 +3,7 @@ import React, { type JSX } from 'react';
 import { DevidedPagePath } from '@growi/core/dist/models';
 import { useTranslation } from 'next-i18next';
 
-import { useCurrentPagePath } from '~/stores/page';
+import { useCurrentPagePath } from '~/states/page';
 
 import type { GetItemProps } from '../interfaces/downshift';
 
@@ -22,7 +22,7 @@ export const SearchMethodMenuItem = (props: Props): JSX.Element => {
 
   const { t } = useTranslation('commons');
 
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
 
   const dPagePath = (new DevidedPagePath(currentPagePath ?? '', true, true));
   const currentPageName = `

+ 2 - 3
apps/app/src/pages/_private-legacy-pages.page.tsx

@@ -11,12 +11,12 @@ import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
+import { useHydratePageAtoms } from '~/states/hydrate/page';
 import { useHydrateSidebarAtoms } from '~/states/hydrate/sidebar';
 import {
   useCsrfToken, useCurrentUser, useIsSearchPage, useIsSearchScopeChildrenAsDefault,
   useIsSearchServiceConfigured, useIsSearchServiceReachable, useRendererConfig, useGrowiCloudUri, useIsEnabledMarp, useCurrentPathname,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 
 import type { CommonProps } from './utils/commons';
 import {
@@ -52,8 +52,7 @@ const PrivateLegacyPage: NextPage<Props> = (props: Props) => {
 
   // clear the cache for the current page
   //  in order to fix https://redmine.weseek.co.jp/issues/135811
-  useSWRxCurrentPage(null);
-  useCurrentPageId(null);
+  useHydratePageAtoms(undefined);
   useCurrentPathname('/_private-legacy-pages');
 
   // Search

+ 2 - 3
apps/app/src/pages/_search.page.tsx

@@ -12,12 +12,12 @@ import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
+import { useHydratePageAtoms } from '~/states/hydrate/page';
 import { useHydrateSidebarAtoms } from '~/states/hydrate/sidebar';
 import {
   useCsrfToken, useCurrentUser, useIsContainerFluid, useIsSearchPage, useIsSearchScopeChildrenAsDefault,
   useIsSearchServiceConfigured, useIsSearchServiceReachable, useRendererConfig, useShowPageLimitationL, useGrowiCloudUri, useCurrentPathname,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 
 import type { NextPageWithLayout } from './_app.page';
 import type { CommonProps } from './utils/commons';
@@ -58,8 +58,7 @@ const SearchResultPage: NextPageWithLayout<Props> = (props: Props) => {
 
   // clear the cache for the current page
   //  in order to fix https://redmine.weseek.co.jp/issues/135811
-  useSWRxCurrentPage(null);
-  useCurrentPageId(null);
+  useHydratePageAtoms(undefined);
   useCurrentPathname('/_search');
 
   // Search

+ 2 - 3
apps/app/src/pages/me/[[...path]].page.tsx

@@ -14,6 +14,7 @@ import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
+import { useHydratePageAtoms } from '~/states/hydrate/page';
 import { useHydrateSidebarAtoms } from '~/states/hydrate/sidebar';
 import {
   useCurrentUser, useIsSearchPage, useGrowiCloudUri,
@@ -21,7 +22,6 @@ import {
   useCsrfToken, useIsSearchScopeChildrenAsDefault,
   useRegistrationWhitelist, useShowPageLimitationXL, useRendererConfig, useIsEnabledMarp, useCurrentPathname,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 
 import type { NextPageWithLayout } from '../_app.page';
@@ -105,8 +105,7 @@ const MePage: NextPageWithLayout<Props> = (props: Props) => {
 
   // clear the cache for the current page
   //  in order to fix https://redmine.weseek.co.jp/issues/135811
-  useSWRxCurrentPage(null);
-  useCurrentPageId(null);
+  useHydratePageAtoms(undefined);
   useCurrentPathname('/me');
 
   // page

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

@@ -20,12 +20,13 @@ import type { RendererConfig } from '~/interfaces/services/renderer';
 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 {
   useCurrentUser, useRendererConfig, useIsSearchPage, useCurrentPathname,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useIsContainerFluid, useIsEnabledMarp,
   useIsLocalAccountRegistrationEnabled, useShowPageSideAuthors,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useIsNotFound, useSWRMUTxCurrentPage } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 
 import type { NextPageWithLayout } from '../_app.page';
@@ -88,11 +89,15 @@ const GrowiContextualSubNavigationForSharedPage = (props: GrowiContextualSubNavi
 };
 
 const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
+  useHydrateSharedPageAtoms({
+    pageId: props.shareLinkRelatedPage?._id,
+    isNotFound: props.isNotFound,
+  });
+
+  const [currentPage] = useCurrentPageData();
   useCurrentPathname(props.shareLink?.relatedPage.path);
   useIsSearchPage(false);
-  useIsNotFound(props.isNotFound);
   useShareLinkId(props.shareLink?._id);
-  useCurrentPageId(props.shareLink?.relatedPage._id);
   useCurrentUser(props.currentUser);
   useRendererConfig(props.rendererConfig);
   useIsSearchServiceConfigured(props.isSearchServiceConfigured);
@@ -103,7 +108,7 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   useShowPageSideAuthors(props.showPageSideAuthors);
   useIsContainerFluid(props.isContainerFluid);
 
-  const { trigger: mutateCurrentPage, data: currentPage } = useSWRMUTxCurrentPage();
+  const { fetchAndUpdatePage } = usePageFetcher();
 
   useEffect(() => {
     if (!props.skipSSR) {
@@ -111,9 +116,9 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
     }
 
     if (props.shareLink?.relatedPage._id != null && !props.isNotFound) {
-      mutateCurrentPage();
+      fetchAndUpdatePage();
     }
-  }, [mutateCurrentPage, props.isNotFound, props.shareLink?.relatedPage._id, props.skipSSR]);
+  }, [fetchAndUpdatePage, props.isNotFound, props.shareLink?.relatedPage._id, props.skipSSR]);
 
 
   const pagePath = props.shareLinkRelatedPage?.path ?? '';

+ 2 - 3
apps/app/src/pages/tags.page.tsx

@@ -15,13 +15,13 @@ import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { IDataTagCount } from '~/interfaces/tag';
+import { useHydratePageAtoms } from '~/states/hydrate/page';
 import { useHydrateSidebarAtoms } from '~/states/hydrate/sidebar';
 import {
   useCurrentUser, useIsSearchPage,
   useIsSearchServiceConfigured, useIsSearchServiceReachable,
   useIsSearchScopeChildrenAsDefault, useGrowiCloudUri, useCurrentPathname,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 import { useSWRxTagsList } from '~/stores/tag';
 
 
@@ -55,8 +55,7 @@ const TagPage: NextPageWithLayout<CommonProps> = (props: Props) => {
 
   // clear the cache for the current page
   //  in order to fix https://redmine.weseek.co.jp/issues/135811
-  useSWRxCurrentPage(null);
-  useCurrentPageId(null);
+  useHydratePageAtoms(undefined);
   useCurrentPathname('/tags');
 
   const { data: tagDataList, error } = useSWRxTagsList(PAGING_LIMIT, offset);

+ 2 - 3
apps/app/src/pages/trash.page.tsx

@@ -12,13 +12,13 @@ import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
+import { useHydratePageAtoms } from '~/states/hydrate/page';
 import { useHydrateSidebarAtoms } from '~/states/hydrate/sidebar';
 import {
   useCurrentUser, useCurrentPathname, useGrowiCloudUri,
   useIsSearchServiceConfigured, useIsSearchServiceReachable,
   useIsSearchScopeChildrenAsDefault, useIsSearchPage, useShowPageLimitationXL,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 
 
 import type { NextPageWithLayout } from './_app.page';
@@ -49,8 +49,7 @@ const TrashPage: NextPageWithLayout<CommonProps> = (props: Props) => {
 
   // clear the cache for the current page
   //  in order to fix https://redmine.weseek.co.jp/issues/135811
-  useSWRxCurrentPage(null);
-  useCurrentPageId(null);
+  useHydratePageAtoms(undefined);
   useCurrentPathname('/trash');
 
   useGrowiCloudUri(props.growiCloudUri);

+ 31 - 2
apps/app/src/states/hydrate/page.ts

@@ -7,7 +7,7 @@ import {
   pageNotFoundAtom,
   latestRevisionAtom,
   templateTagsAtom,
-  templateContentAtom,
+  templateBodyAtom,
   remoteRevisionIdAtom,
   remoteRevisionBodyAtom,
 } from '../page/internal-atoms';
@@ -53,10 +53,39 @@ export const useHydratePageAtoms = (
 
     // Template data - from options (not auto-extracted from page)
     [templateTagsAtom, options?.templateTags ?? []],
-    [templateContentAtom, options?.templateBody ?? ''],
+    [templateBodyAtom, options?.templateBody ?? ''],
 
     // Remote revision data - auto-extracted from page.revision
     [remoteRevisionIdAtom, page?.revision?._id],
     [remoteRevisionBodyAtom, page?.revision?.body],
   ]);
 };
+
+/**
+ * Hook for hydrating shared page atoms with server-side data
+ * This is a simplified version that focuses on the most common use case:
+ * hydrating with page ID and not found status
+ * @param args
+ */
+export const useHydrateSharedPageAtoms = (
+    args: {
+      pageId: string | undefined;
+      isNotFound: boolean;
+    },
+): void => {
+  useHydrateAtoms([
+    // Core page state - automatically extract from page object
+    [currentPageIdAtom, args.pageId],
+    [currentPageDataAtom, undefined],
+    [pageNotFoundAtom, args.isNotFound],
+    [latestRevisionAtom, true],
+
+    // Template data - from options (not auto-extracted from page)
+    [templateTagsAtom, []],
+    [templateBodyAtom, ''],
+
+    // Remote revision data - auto-extracted from page.revision
+    [remoteRevisionIdAtom, undefined],
+    [remoteRevisionBodyAtom, undefined],
+  ]);
+};

+ 11 - 25
apps/app/src/states/page/hooks.ts

@@ -1,4 +1,4 @@
-import type { IPagePopulatedToShowRevision } from '@growi/core';
+import type { IPagePopulatedToShowRevision, IUserHasId } from '@growi/core';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { useAtom } from 'jotai';
 
@@ -13,16 +13,15 @@ import {
   pageNotFoundAtom,
   latestRevisionAtom,
   setCurrentPageAtom,
-  setPageStatusAtom,
   // New atoms for enhanced functionality
   remoteRevisionIdAtom,
   remoteRevisionBodyAtom,
   remoteRevisionLastUpdateUserAtom,
   remoteRevisionLastUpdatedAtAtom,
-  setTemplateDataAtom,
-  setRemoteRevisionDataAtom,
   isTrashPageAtom,
   isRevisionOutdatedAtom,
+  templateTagsAtom,
+  templateBodyAtom,
 } from './internal-atoms';
 
 /**
@@ -47,30 +46,17 @@ export const useLatestRevision = (): UseAtom<typeof latestRevisionAtom> => {
   return useAtom(latestRevisionAtom);
 };
 
-export const useCurrentPagePath = (): readonly [string | undefined, never] => {
-  return useAtom(currentPagePathAtom);
-};
-
 // Write hooks for updating page state
 export const useSetCurrentPage = (): ((page: IPagePopulatedToShowRevision | undefined) => void) => {
   return useAtom(setCurrentPageAtom)[1];
 };
 
-export const useSetPageStatus = (): ((status: { isNotFound?: boolean; isLatestRevision?: boolean }) => void) => {
-  return useAtom(setPageStatusAtom)[1];
-};
-
-export const useSetTemplateData = (): ((data: { tags?: string[]; body?: string }) => void) => {
-  return useAtom(setTemplateDataAtom)[1];
+export const useTemplateTags = (): UseAtom<typeof templateTagsAtom> => {
+  return useAtom(templateTagsAtom);
 };
 
-export const useSetRemoteRevisionData = (): ((data: {
-  id?: string | null;
-  body?: string | null;
-  lastUpdateUser?: any;
-  lastUpdatedAt?: Date | null;
-}) => void) => {
-  return useAtom(setRemoteRevisionDataAtom)[1];
+export const useTemplateBody = (): UseAtom<typeof templateBodyAtom> => {
+  return useAtom(templateBodyAtom);
 };
 
 // Remote revision hooks (replacements for stores/remote-latest-page.ts)
@@ -96,17 +82,17 @@ export const useRemoteRevisionLastUpdatedAt = (): UseAtom<typeof remoteRevisionL
  * Get current page path with fallback to pathname
  * Pure Jotai replacement for stores/page.tsx useCurrentPagePath
  */
-export const useCurrentPagePathWithFallback = (): string | undefined => {
+export const useCurrentPagePath = (): readonly [string | undefined] => {
   const [currentPagePath] = useAtom(currentPagePathAtom);
   const { data: currentPathname } = useCurrentPathname();
 
   if (currentPagePath != null) {
-    return currentPagePath;
+    return [currentPagePath];
   }
   if (currentPathname != null && !pagePathUtils.isPermalink(currentPathname)) {
-    return currentPathname;
+    return [currentPathname];
   }
-  return undefined;
+  return [undefined];
 };
 
 /**

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

@@ -13,16 +13,11 @@ export {
   usePageNotFound,
   useLatestRevision,
   useSetCurrentPage,
-  useSetPageStatus,
-  useSetTemplateData,
-  useSetRemoteRevisionData,
   // Remote revision hooks (replacements for stores/remote-latest-page.ts)
   useRemoteRevisionId,
   useRemoteRevisionBody,
   useRemoteRevisionLastUpdateUser,
   useRemoteRevisionLastUpdatedAt,
-  // Enhanced computed hooks (pure Jotai replacements for stores/page.tsx)
-  useCurrentPagePathWithFallback,
   useIsTrashPage,
   useIsRevisionOutdated,
 } from './hooks';
@@ -32,11 +27,5 @@ export {
   usePageFetcher,
 } from './page-fetcher';
 
-// Template data atoms (these need to be directly accessible for some use cases)
-export {
-  templateTagsAtom,
-  templateContentAtom,
-} from './internal-atoms';
-
 // Re-export types that external consumers might need
 export type { UseAtom } from '../ui/helper';

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

@@ -15,7 +15,7 @@ export const latestRevisionAtom = atom(true);
 
 // Template data atoms (internal)
 export const templateTagsAtom = atom<string[]>([]);
-export const templateContentAtom = atom<string>('');
+export const templateBodyAtom = atom<string>('');
 
 // Derived atoms for computed states
 export const currentPagePathAtom = atom((get) => {
@@ -76,14 +76,14 @@ export const setPageStatusAtom = atom(
 );
 
 // Update atoms for template and remote revision data
-export const setTemplateDataAtom = atom(
+export const setTemplateContentAtom = atom(
   null,
   (get, set, data: { tags?: string[]; body?: string }) => {
     if (data.tags !== undefined) {
       set(templateTagsAtom, data.tags);
     }
     if (data.body !== undefined) {
-      set(templateContentAtom, data.body);
+      set(templateBodyAtom, data.body);
     }
   },
 );

+ 2 - 2
apps/app/src/stores-universal/ui.tsx

@@ -8,7 +8,7 @@ import type { SWRResponseWithUtils } from '@growi/core/dist/swr';
 import { isServer } from '@growi/core/dist/utils';
 import useSWRImmutable from 'swr/immutable';
 
-import { useIsNotFound } from '~/stores/page';
+import { usePageNotFound } from '~/states/page';
 
 import { useIsEditable } from './context';
 
@@ -70,7 +70,7 @@ type EditorModeUtils = {
 
 export const useEditorMode = (): SWRResponseWithUtils<EditorModeUtils, EditorMode> => {
   const { data: _isEditable } = useIsEditable();
-  const { data: isNotFound } = useIsNotFound();
+  const [isNotFound] = usePageNotFound();
 
   const editorModeByHash = determineEditorModeByHash();
 

+ 11 - 6
apps/app/src/stores/remote-latest-page.ts

@@ -4,6 +4,8 @@ import type { IUserHasId } from '@growi/core';
 import { useSWRStatic } from '@growi/core/dist/swr';
 import type { SWRResponse } from 'swr';
 
+import { useRemoteRevisionBody, useRemoteRevisionId } from '~/states/page';
+
 export const useRemoteRevisionLastUpdateUser = (initialData?: IUserHasId): SWRResponse<IUserHasId, Error> => {
   return useSWRStatic<IUserHasId, Error>('remoteRevisionLastUpdateUser', initialData);
 };
@@ -13,6 +15,8 @@ export const useRemoteRevisionLastUpdatedAt = (initialData?: Date): SWRResponse<
 };
 
 export type RemoteRevisionData = {
+  remoteRevisionId: string,
+  remoteRevisionBody: string,
   remoteRevisionLastUpdateUser?: IUserHasId,
   remoteRevisionLastUpdatedAt: Date,
 }
@@ -20,16 +24,17 @@ export type RemoteRevisionData = {
 
 // set remote data all at once
 export const useSetRemoteLatestPageData = (): { setRemoteLatestPageData: (pageData: RemoteRevisionData) => void } => {
+  const [, setRemoteRevisionId] = useRemoteRevisionId();
+  const [, setRemoteRevisionBody] = useRemoteRevisionBody();
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
   const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
 
   const setRemoteLatestPageData = useCallback((remoteRevisionData: RemoteRevisionData) => {
-    const {
-      remoteRevisionLastUpdateUser, remoteRevisionLastUpdatedAt,
-    } = remoteRevisionData;
-    mutateRemoteRevisionLastUpdateUser(remoteRevisionLastUpdateUser);
-    mutateRemoteRevisionLastUpdatedAt(remoteRevisionLastUpdatedAt);
-  }, [mutateRemoteRevisionLastUpdateUser, mutateRemoteRevisionLastUpdatedAt]);
+    setRemoteRevisionId(remoteRevisionData.remoteRevisionId);
+    setRemoteRevisionBody(remoteRevisionData.remoteRevisionBody);
+    mutateRemoteRevisionLastUpdateUser(remoteRevisionData.remoteRevisionLastUpdateUser);
+    mutateRemoteRevisionLastUpdatedAt(remoteRevisionData.remoteRevisionLastUpdatedAt);
+  }, [mutateRemoteRevisionLastUpdateUser, mutateRemoteRevisionLastUpdatedAt, setRemoteRevisionBody, setRemoteRevisionId]);
 
   return useMemo(() => {
     return {

+ 4 - 4
apps/app/src/stores/renderer.tsx

@@ -27,7 +27,7 @@ const useRendererConfigExt = (): RendererConfigExt | null => {
 
 
 export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
   const rendererConfig = useRendererConfigExt();
   const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
 
@@ -82,7 +82,7 @@ export const useTocOptions = (): SWRResponse<RendererOptions, Error> => {
 };
 
 export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
   const rendererConfig = useRendererConfigExt();
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
@@ -109,7 +109,7 @@ export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
 };
 
 export const useCommentForCurrentPageOptions = (): SWRResponse<RendererOptions, Error> => {
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
   const rendererConfig = useRendererConfigExt();
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
@@ -182,7 +182,7 @@ export const useCustomSidebarOptions = (config?: SWRConfiguration): SWRResponse<
 };
 
 export const usePresentationViewOptions = (): SWRResponse<RendererOptions, Error> => {
-  const { data: currentPagePath } = useCurrentPagePath();
+  const [currentPagePath] = useCurrentPagePath();
   const rendererConfig = useRendererConfigExt();
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null;