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

WIP: improve jotai atom exports

Yuki Takei 7 месяцев назад
Родитель
Сommit
d08b56b969
30 измененных файлов с 511 добавлено и 598 удалено
  1. 6 6
      apps/app/src/pages/[[...path]]/index.page.tsx
  2. 8 6
      apps/app/src/pages/_app.page.tsx
  3. 17 0
      apps/app/src/pages/common-props/commons.ts
  4. 1 0
      apps/app/src/pages/common-props/index.ts
  5. 1 1
      apps/app/src/pages/utils/nextjs-routing-utils.ts
  6. 3 2
      apps/app/src/services/layout/use-should-expand-content.ts
  7. 20 40
      apps/app/src/states/context.ts
  8. 0 41
      apps/app/src/states/global/auto-update.ts
  9. 206 127
      apps/app/src/states/global/global.ts
  10. 50 17
      apps/app/src/states/global/hydrate.ts
  11. 0 11
      apps/app/src/states/helper.ts
  12. 24 56
      apps/app/src/states/page/hooks.ts
  13. 18 17
      apps/app/src/states/page/index.ts
  14. 1 5
      apps/app/src/states/page/redirect.ts
  15. 1 1
      apps/app/src/states/page/use-current-page-loading.ts
  16. 84 28
      apps/app/src/states/page/use-fetch-current-page.spec.tsx
  17. 1 1
      apps/app/src/states/page/use-page-loading.ts
  18. 2 160
      apps/app/src/states/server-configurations/server-configurations.ts
  19. 2 4
      apps/app/src/states/socket-io/socket-io.ts
  20. 7 8
      apps/app/src/states/ui/device.ts
  21. 1 1
      apps/app/src/states/ui/editor/atoms.ts
  22. 3 5
      apps/app/src/states/ui/editor/hooks.ts
  23. 4 3
      apps/app/src/states/ui/editor/index.ts
  24. 5 14
      apps/app/src/states/ui/sidebar/sidebar.ts
  25. 5 4
      apps/app/src/stores-universal/context.tsx
  26. 2 2
      apps/app/src/stores-universal/use-next-themes.tsx
  27. 6 5
      apps/app/src/stores/editor.tsx
  28. 6 6
      apps/app/src/stores/page.tsx
  29. 6 6
      apps/app/src/stores/renderer.tsx
  30. 21 21
      apps/app/src/stores/ui.tsx

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

@@ -90,11 +90,11 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   const pageData = isInitialProps(props) ? props.pageWithMeta?.data : undefined;
   const pageData = isInitialProps(props) ? props.pageWithMeta?.data : undefined;
   useHydratePageAtoms(pageData);
   useHydratePageAtoms(pageData);
 
 
-  const [currentPage] = useCurrentPageData();
-  const [pageId] = useCurrentPageId();
-  const [currentPagePath] = useCurrentPagePath();
-  const [isNotFound] = usePageNotFound();
-  const [rendererConfig] = useRendererConfig();
+  const currentPage = useCurrentPageData();
+  const pageId = useCurrentPageId();
+  const currentPagePath = useCurrentPagePath();
+  const isNotFound = usePageNotFound();
+  const rendererConfig = useRendererConfig();
   const [, setRedirectFrom] = useRedirectFrom();
   const [, setRedirectFrom] = useRedirectFrom();
   const [, setEditingMarkdown] = useEditingMarkdown();
   const [, setEditingMarkdown] = useEditingMarkdown();
 
 
@@ -184,7 +184,7 @@ const Layout = ({ children, ...props }: LayoutProps): JSX.Element => {
 Page.getLayout = function getLayout(page: React.ReactElement<Props>) {
 Page.getLayout = function getLayout(page: React.ReactElement<Props>) {
   // Get drawioUri from rendererConfig atom to ensure consistency across navigations
   // Get drawioUri from rendererConfig atom to ensure consistency across navigations
   const DrawioViewerScriptWithAtom = (): JSX.Element => {
   const DrawioViewerScriptWithAtom = (): JSX.Element => {
-    const [rendererConfig] = useRendererConfig();
+    const rendererConfig = useRendererConfig();
     return <DrawioViewerScript drawioUri={rendererConfig.drawioUri} />;
     return <DrawioViewerScript drawioUri={rendererConfig.drawioUri} />;
   };
   };
 
 

+ 8 - 6
apps/app/src/pages/_app.page.tsx

@@ -14,10 +14,11 @@ import * as nextI18nConfig from '^/config/next-i18next.config';
 
 
 import { GlobalFonts } from '~/components/FontFamily/GlobalFonts';
 import { GlobalFonts } from '~/components/FontFamily/GlobalFonts';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
-import { useAutoUpdateGlobalAtoms } from '~/states/global/auto-update';
-import { useHydrateGlobalInitialAtoms } from '~/states/global/hydrate';
+import { useHydrateGlobalEachAtoms, useHydrateGlobalInitialAtoms } from '~/states/global/hydrate';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
 
 
+import type { CommonEachProps, CommonInitialProps } from './common-props';
+import { isCommonInitialProps } from './common-props';
 import { getLocaleAtServerSide } from './utils/locale';
 import { getLocaleAtServerSide } from './utils/locale';
 import { useNextjsRoutingPageRegister } from './utils/nextjs-routing-utils';
 import { useNextjsRoutingPageRegister } from './utils/nextjs-routing-utils';
 import { registerTransformerForObjectId } from './utils/objectid-transformer';
 import { registerTransformerForObjectId } from './utils/objectid-transformer';
@@ -44,8 +45,9 @@ export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
   getLayout?: (page: JSX.Element) => ReactNode,
   getLayout?: (page: JSX.Element) => ReactNode,
 }
 }
 
 
-type GrowiAppProps = AppProps & {
-  Component: NextPageWithLayout,
+type CombinedCommonProps = CommonEachProps | (CommonEachProps & CommonInitialProps);
+type GrowiAppProps = AppProps<CombinedCommonProps> & {
+  Component: NextPageWithLayout<CombinedCommonProps>,
   userLocale: Locale,
   userLocale: Locale,
 };
 };
 
 
@@ -53,8 +55,8 @@ const GrowiAppSubstance = ({ Component, pageProps, userLocale }: GrowiAppProps):
   const router = useRouter();
   const router = useRouter();
 
 
   // Hydrate global atoms with server-side data
   // Hydrate global atoms with server-side data
-  useHydrateGlobalInitialAtoms(pageProps);
-  useAutoUpdateGlobalAtoms(pageProps);
+  useHydrateGlobalInitialAtoms(isCommonInitialProps(pageProps) ? pageProps : undefined);
+  useHydrateGlobalEachAtoms(pageProps);
 
 
   useNextjsRoutingPageRegister(pageProps.nextjsRoutingPage);
   useNextjsRoutingPageRegister(pageProps.nextjsRoutingPage);
 
 

+ 17 - 0
apps/app/src/pages/common-props/commons.ts

@@ -50,6 +50,23 @@ export const getServerSideCommonInitialProps: GetServerSideProps<CommonInitialPr
   };
   };
 };
 };
 
 
+export const isCommonInitialProps = (props: unknown): props is CommonInitialProps => {
+  if (typeof props !== 'object' || props === null) {
+    logger.warn('isCommonInitialProps: props is not an object or is null');
+    return false;
+  }
+
+  const p = props as Record<string, unknown>;
+
+  // Essential properties validation
+  if (p.isNextjsRoutingTypeInitial !== true) {
+    logger.warn('isCommonInitialProps: isNextjsRoutingTypeInitial is not true', { isNextjsRoutingTypeInitial: p.isNextjsRoutingTypeInitial });
+    return false;
+  }
+
+  return true;
+};
+
 export type CommonEachProps = {
 export type CommonEachProps = {
   currentPathname: string,
   currentPathname: string,
   nextjsRoutingPage?: string, // must be set by each page
   nextjsRoutingPage?: string, // must be set by each page

+ 1 - 0
apps/app/src/pages/common-props/index.ts

@@ -1,5 +1,6 @@
 export {
 export {
   type CommonInitialProps, getServerSideCommonInitialProps,
   type CommonInitialProps, getServerSideCommonInitialProps,
   type CommonEachProps, getServerSideCommonEachProps,
   type CommonEachProps, getServerSideCommonEachProps,
+  isCommonInitialProps,
 } from './commons';
 } from './commons';
 export { getServerSideI18nProps } from './i18n';
 export { getServerSideI18nProps } from './i18n';

+ 1 - 1
apps/app/src/pages/utils/nextjs-routing-utils.ts

@@ -5,7 +5,7 @@ import { type GetServerSidePropsContext } from 'next';
 
 
 const COOKIE_NAME = 'nextjsRoutingPage';
 const COOKIE_NAME = 'nextjsRoutingPage';
 
 
-export const useNextjsRoutingPageRegister = (nextjsRoutingPage: string | null): void => {
+export const useNextjsRoutingPageRegister = (nextjsRoutingPage: string | undefined): void => {
   useEffect(() => {
   useEffect(() => {
     if (nextjsRoutingPage == null) {
     if (nextjsRoutingPage == null) {
       Cookies.remove(COOKIE_NAME);
       Cookies.remove(COOKIE_NAME);

+ 3 - 2
apps/app/src/services/layout/use-should-expand-content.ts

@@ -1,9 +1,10 @@
 import type { IPage, IPagePopulatedToShowRevision } from '@growi/core';
 import type { IPage, IPagePopulatedToShowRevision } from '@growi/core';
+import { useAtomValue } from 'jotai';
 
 
-import { useIsContainerFluid } from '~/states/server-configurations';
+import { isContainerFluidAtom } from '~/states/server-configurations';
 
 
 const useDetermineExpandContent = (expandContentWidth?: boolean | null): boolean => {
 const useDetermineExpandContent = (expandContentWidth?: boolean | null): boolean => {
-  const [dataIsContainerFluid] = useIsContainerFluid();
+  const dataIsContainerFluid = useAtomValue(isContainerFluidAtom);
 
 
   const isContainerFluidDefault = dataIsContainerFluid;
   const isContainerFluidDefault = dataIsContainerFluid;
   return expandContentWidth ?? isContainerFluidDefault ?? false;
   return expandContentWidth ?? isContainerFluidDefault ?? false;

+ 20 - 40
apps/app/src/states/context.ts

@@ -1,100 +1,85 @@
-import { atom, useAtom } from 'jotai';
-
-import { currentUserAtom, growiCloudUriAtom } from './global';
-import type { UseAtom } from './helper';
+import { atom, useAtomValue } from 'jotai';
+import { currentUserAtomGetter, growiCloudUriAtomGetter } from './global';
 
 
 /**
 /**
  * Atom for checking if current path is identical
  * Atom for checking if current path is identical
  */
  */
 const isIdenticalPathAtom = atom<boolean>(false);
 const isIdenticalPathAtom = atom<boolean>(false);
 
 
-export const useIsIdenticalPath = (): UseAtom<typeof isIdenticalPathAtom> => {
-  return useAtom(isIdenticalPathAtom);
-};
+export const useIsIdenticalPath = () => useAtomValue(isIdenticalPathAtom);
 
 
 /**
 /**
  * Atom for checking if current page is forbidden
  * Atom for checking if current page is forbidden
  */
  */
 const isForbiddenAtom = atom<boolean>(false);
 const isForbiddenAtom = atom<boolean>(false);
 
 
-export const useIsForbidden = (): UseAtom<typeof isForbiddenAtom> => {
-  return useAtom(isForbiddenAtom);
-};
+export const useIsForbidden = () => useAtomValue(isForbiddenAtom);
 
 
 /**
 /**
  * Atom for checking if current page is not creatable
  * Atom for checking if current page is not creatable
  */
  */
 const isNotCreatableAtom = atom<boolean>(false);
 const isNotCreatableAtom = atom<boolean>(false);
 
 
-export const useIsNotCreatable = (): UseAtom<typeof isNotCreatableAtom> => {
-  return useAtom(isNotCreatableAtom);
-};
+export const useIsNotCreatable = () => useAtomValue(isNotCreatableAtom);
 
 
 /**
 /**
  * Computed atom for checking if current user is a guest user
  * Computed atom for checking if current user is a guest user
  * Depends on currentUser atom
  * Depends on currentUser atom
  */
  */
 const isGuestUserAtom = atom((get) => {
 const isGuestUserAtom = atom((get) => {
-  const currentUser = get(currentUserAtom);
+  const currentUser = get(currentUserAtomGetter);
   return currentUser?._id == null;
   return currentUser?._id == null;
 });
 });
 
 
-export const useIsGuestUser = (): UseAtom<typeof isGuestUserAtom> => {
-  return useAtom(isGuestUserAtom);
-};
+// export const useIsGuestUser = () => {
+//   return useAtom(isGuestUserAtom);
+// };
+export const useIsGuestUser = () => useAtomValue(isGuestUserAtom);
 
 
 /**
 /**
  * Computed atom for checking if current user is a read-only user
  * Computed atom for checking if current user is a read-only user
  * Depends on currentUser and isGuestUser atoms
  * Depends on currentUser and isGuestUser atoms
  */
  */
 const isReadOnlyUserAtom = atom((get) => {
 const isReadOnlyUserAtom = atom((get) => {
-  const currentUser = get(currentUserAtom);
+  const currentUser = get(currentUserAtomGetter);
   const isGuestUser = get(isGuestUserAtom);
   const isGuestUser = get(isGuestUserAtom);
 
 
   return !isGuestUser && !!currentUser?.readOnly;
   return !isGuestUser && !!currentUser?.readOnly;
 });
 });
 
 
-export const useIsReadOnlyUser = (): UseAtom<typeof isReadOnlyUserAtom> => {
-  return useAtom(isReadOnlyUserAtom);
-};
+export const useIsReadOnlyUser = () => useAtomValue(isReadOnlyUserAtom);
 
 
 /**
 /**
  * Computed atom for checking if current user is an admin
  * Computed atom for checking if current user is an admin
  * Depends on currentUser atom
  * Depends on currentUser atom
  */
  */
 const isAdminAtom = atom((get) => {
 const isAdminAtom = atom((get) => {
-  const currentUser = get(currentUserAtom);
+  const currentUser = get(currentUserAtomGetter);
   return currentUser?.admin ?? false;
   return currentUser?.admin ?? false;
 });
 });
 
 
-export const useIsAdmin = (): UseAtom<typeof isAdminAtom> => {
-  return useAtom(isAdminAtom);
-};
+export const useIsAdmin = () => useAtomValue(isAdminAtom);
 
 
 /**
 /**
  * Atom for checking if current user is a shared user
  * Atom for checking if current user is a shared user
  */
  */
 const isSharedUserAtom = atom<boolean>(false);
 const isSharedUserAtom = atom<boolean>(false);
 
 
-export const useIsSharedUser = (): UseAtom<typeof isSharedUserAtom> => {
-  return useAtom(isSharedUserAtom);
-};
+export const useIsSharedUser = () => useAtomValue(isSharedUserAtom);
 
 
 /**
 /**
  * Atom for checking if current page is a search page
  * Atom for checking if current page is a search page
  */
  */
 const isSearchPageAtom = atom<boolean | null>(null);
 const isSearchPageAtom = atom<boolean | null>(null);
 
 
-export const useIsSearchPage = (): UseAtom<typeof isSearchPageAtom> => {
-  return useAtom(isSearchPageAtom);
-};
+export const useIsSearchPage = () => useAtomValue(isSearchPageAtom);
 
 
 /**
 /**
  * Computed atom for GROWI documentation URL
  * Computed atom for GROWI documentation URL
  * Depends on growiCloudUri atom
  * Depends on growiCloudUri atom
  */
  */
 const growiDocumentationUrlAtom = atom((get) => {
 const growiDocumentationUrlAtom = atom((get) => {
-  const growiCloudUri = get(growiCloudUriAtom);
+  const growiCloudUri = get(growiCloudUriAtomGetter);
 
 
   if (growiCloudUri != null) {
   if (growiCloudUri != null) {
     const url = new URL('/help', growiCloudUri);
     const url = new URL('/help', growiCloudUri);
@@ -104,11 +89,8 @@ const growiDocumentationUrlAtom = atom((get) => {
   return 'https://docs.growi.org';
   return 'https://docs.growi.org';
 });
 });
 
 
-export const useGrowiDocumentationUrl = (): UseAtom<
-  typeof growiDocumentationUrlAtom
-> => {
-  return useAtom(growiDocumentationUrlAtom);
-};
+export const useGrowiDocumentationUrl = () =>
+  useAtomValue(growiDocumentationUrlAtom);
 
 
 /**
 /**
  * Computed atom for checking if current page is editable
  * Computed atom for checking if current page is editable
@@ -130,6 +112,4 @@ const isEditableAtom = atom((get) => {
   );
   );
 });
 });
 
 
-export const useIsEditable = (): UseAtom<typeof isEditableAtom> => {
-  return useAtom(isEditableAtom);
-};
+export const useIsEditable = () => useAtomValue(isEditableAtom);

+ 0 - 41
apps/app/src/states/global/auto-update.ts

@@ -1,41 +0,0 @@
-import { useIsomorphicLayoutEffect } from 'usehooks-ts';
-
-import type { CommonEachProps } from '~/pages/common-props';
-
-import {
-  useCsrfToken,
-  useCurrentPathname,
-  useCurrentUser,
-  useIsMaintenanceMode,
-} from './global';
-
-/**
- * Hook for auto-updating global UI state atoms with server-side data
- *
- * @param props - Server-side common properties from getServerSideCommonEachProps
- */
-export const useAutoUpdateGlobalAtoms = (props: CommonEachProps): void => {
-  // Update pathname and user atoms
-  const [, setCurrentPathname] = useCurrentPathname();
-  useIsomorphicLayoutEffect(() => {
-    setCurrentPathname(props.currentPathname);
-  }, [setCurrentPathname, props.currentPathname]);
-
-  // Update user atom
-  const [, setCurrentUser] = useCurrentUser();
-  useIsomorphicLayoutEffect(() => {
-    setCurrentUser(props.currentUser);
-  }, [setCurrentUser, props.currentUser]);
-
-  // Update CSRF token atom
-  const [, setCsrfToken] = useCsrfToken();
-  useIsomorphicLayoutEffect(() => {
-    setCsrfToken(props.csrfToken);
-  }, [setCsrfToken, props.csrfToken]);
-
-  // Update maintenance mode atom
-  const [, setIsMaintenanceMode] = useIsMaintenanceMode();
-  useIsomorphicLayoutEffect(() => {
-    setIsMaintenanceMode(props.isMaintenanceMode);
-  }, [setIsMaintenanceMode, props.isMaintenanceMode]);
-};

+ 206 - 127
apps/app/src/states/global/global.ts

@@ -1,131 +1,210 @@
 import type { ColorScheme, IUserHasId } from '@growi/core';
 import type { ColorScheme, IUserHasId } from '@growi/core';
-import { atom, useAtom } from 'jotai';
+import { atom, useAtomValue } from 'jotai';
 import type { SupportedActionType } from '~/interfaces/activity';
 import type { SupportedActionType } from '~/interfaces/activity';
-import type { UseAtom } from '../helper';
 
 
-// CSRF Token atom (no persistence needed as it's server-provided)
-export const csrfTokenAtom = atom<string>('');
-export const useCsrfToken = (): UseAtom<typeof csrfTokenAtom> => {
-  return useAtom(csrfTokenAtom);
-};
-
-// App current pathname atom (no persistence needed as it's server-provided)
-export const currentPathnameAtom = atom<string>('');
-export const useCurrentPathname = (): UseAtom<typeof currentPathnameAtom> => {
-  return useAtom(currentPathnameAtom);
-};
-
-// Current User atom (no persistence needed as it's server-provided)
-export const currentUserAtom = atom<IUserHasId | undefined>();
-export const useCurrentUser = (): UseAtom<typeof currentUserAtom> => {
-  return useAtom(currentUserAtom);
-};
-
-// App Title atom (no persistence needed as it's server-provided)
-export const appTitleAtom = atom<string>('');
-export const useAppTitle = (): UseAtom<typeof appTitleAtom> => {
-  return useAtom(appTitleAtom);
-};
-
-// Custom Title Template atom (no persistence needed as it's server-provided)
-export const customTitleTemplateAtom = atom<string>('');
-export const useCustomTitleTemplate = (): UseAtom<
-  typeof customTitleTemplateAtom
-> => {
-  return useAtom(customTitleTemplateAtom);
-};
-
-// Site URL atom (no persistence needed as it's server-provided)
-export const siteUrlAtom = atom<string | undefined>(undefined);
-export const useSiteUrl = (): UseAtom<typeof siteUrlAtom> => {
-  return useAtom(siteUrlAtom);
-};
-
-// Site URL atom (no persistence needed as it's server-provided)
-export const siteUrlWithEmptyValueWarnAtom = atom<string>('');
-export const useSiteUrlWithEmptyValueWarn = (): UseAtom<
-  typeof siteUrlWithEmptyValueWarnAtom
-> => {
-  return useAtom(siteUrlWithEmptyValueWarnAtom);
-};
-
-// Confidential atom (no persistence needed as it's server-provided)
-export const confidentialAtom = atom<string>('');
-export const useConfidential = (): UseAtom<typeof confidentialAtom> => {
-  return useAtom(confidentialAtom);
-};
-
-// GROWI Version atom (no persistence needed as it's server-provided)
-export const growiVersionAtom = atom<string>('');
-export const useGrowiVersion = (): UseAtom<typeof growiVersionAtom> => {
-  return useAtom(growiVersionAtom);
-};
-
-// Maintenance Mode atom (no persistence needed as it's server-provided)
-export const isMaintenanceModeAtom = atom<boolean>(false);
-export const useIsMaintenanceMode = (): UseAtom<
-  typeof isMaintenanceModeAtom
-> => {
-  return useAtom(isMaintenanceModeAtom);
-};
-
-// Default Logo atom (no persistence needed as it's server-provided)
-export const isDefaultLogoAtom = atom<boolean>(true);
-export const useIsDefaultLogo = (): UseAtom<typeof isDefaultLogoAtom> => {
-  return useAtom(isDefaultLogoAtom);
-};
-
-// Customize Title (admin customize setting)
-export const customizeTitleAtom = atom<string | undefined>(undefined);
-export const useCustomizeTitle = (): UseAtom<typeof customizeTitleAtom> => {
-  return useAtom(customizeTitleAtom);
-};
-
-// Is Customized Logo Uploaded
-export const isCustomizedLogoUploadedAtom = atom<boolean>(false);
-export const useIsCustomizedLogoUploaded = (): UseAtom<
-  typeof isCustomizedLogoUploadedAtom
-> => {
-  return useAtom(isCustomizedLogoUploadedAtom);
-};
-
-// GROWI Cloud URI atom (no persistence needed as it's server-provided)
-export const growiCloudUriAtom = atom<string | undefined>(undefined);
-export const useGrowiCloudUri = (): UseAtom<typeof growiCloudUriAtom> => {
-  return useAtom(growiCloudUriAtom);
-};
-
-// GROWI Cloud App ID atom (no persistence needed as it's server-provided)
-export const growiAppIdForGrowiCloudAtom = atom<number | undefined>(undefined);
-export const useGrowiAppIdForGrowiCloud = (): UseAtom<
-  typeof growiAppIdForGrowiCloudAtom
-> => {
-  return useAtom(growiAppIdForGrowiCloudAtom);
-};
-
-// Forced Color Scheme atom (no persistence needed as it's server-provided)
-export const forcedColorSchemeAtom = atom<ColorScheme | undefined>(undefined);
-export const useForcedColorScheme = (): UseAtom<
-  typeof forcedColorSchemeAtom
-> => {
-  return useAtom(forcedColorSchemeAtom);
-};
-
-export const auditLogEnabledAtom = atom<boolean>(false);
-export const useAuditLogEnabled = (): UseAtom<typeof auditLogEnabledAtom> => {
-  return useAtom(auditLogEnabledAtom);
-};
-
-export const activityExpirationSecondsAtom = atom<number>(0);
-export const useActivityExpirationSeconds = (): UseAtom<
-  typeof activityExpirationSecondsAtom
-> => {
-  return useAtom(activityExpirationSecondsAtom);
-};
-
-export const auditLogAvailableActionsAtom = atom<SupportedActionType[]>([]);
-export const useAuditLogAvailableActions = (): UseAtom<
-  typeof auditLogAvailableActionsAtom
-> => {
-  return useAtom(auditLogAvailableActionsAtom);
+/**
+ * CSRF Token atom
+ */
+const csrfTokenAtom = atom<string>('');
+/**
+ * CSRF Token atom setter
+ */
+export const useCsrfToken = () => useAtomValue(csrfTokenAtom);
+
+/**
+ * App current pathname atom
+ */
+const currentPathnameAtom = atom<string>('');
+/**
+ * App current pathname atom setter
+ */
+export const useCurrentPathname = () => useAtomValue(currentPathnameAtom);
+
+/**
+ * Current User atom
+ */
+const currentUserAtom = atom<IUserHasId | undefined>();
+/**
+ * Current user atom getter
+ */
+export const currentUserAtomGetter = atom((get) => get(currentUserAtom));
+/**
+ * Current User atom setter
+ */
+export const useCurrentUser = () => useAtomValue(currentUserAtom);
+
+/**
+ * App Title atom
+ */
+const appTitleAtom = atom<string>('');
+/**
+ * App Title atom setter
+ */
+export const useAppTitle = () => useAtomValue(appTitleAtom);
+
+/**
+ * Custom Title Template atom
+ */
+const customTitleTemplateAtom = atom<string>('');
+/**
+ * Custom Title Template atom setter
+ */
+export const useCustomTitleTemplate = () =>
+  useAtomValue(customTitleTemplateAtom);
+
+/**
+ * Site URL atom
+ */
+const siteUrlAtom = atom<string | undefined>(undefined);
+/**
+ * Site URL atom setter
+ */
+export const useSiteUrl = () => useAtomValue(siteUrlAtom);
+
+/**
+ * Site URL atom
+ */
+const siteUrlWithEmptyValueWarnAtom = atom<string>('');
+/**
+ * Site URL with empty value warning atom setter
+ */
+export const useSiteUrlWithEmptyValueWarn = () =>
+  useAtomValue(siteUrlWithEmptyValueWarnAtom);
+
+/**
+ * Confidential atom
+ */
+const confidentialAtom = atom<string>('');
+/**
+ * Confidential atom setter
+ */
+export const useConfidential = () => useAtomValue(confidentialAtom);
+
+/**
+ * GROWI Version atom
+ */
+const growiVersionAtom = atom<string>('');
+/**
+ * GROWI Version atom setter
+ */
+export const useGrowiVersion = () => useAtomValue(growiVersionAtom);
+
+/**
+ * Maintenance Mode atom
+ */
+const isMaintenanceModeAtom = atom<boolean>(false);
+/**
+ * Maintenance Mode atom setter
+ */
+export const useIsMaintenanceMode = () => useAtomValue(isMaintenanceModeAtom);
+
+/**
+ * Default Logo atom
+ */
+const isDefaultLogoAtom = atom<boolean>(true);
+/**
+ * Default Logo atom setter
+ */
+export const useIsDefaultLogo = () => useAtomValue(isDefaultLogoAtom);
+
+/**
+ * Customize Title
+ */
+const customizeTitleAtom = atom<string | undefined>(undefined);
+/**
+ * Customize Title atom setter
+ */
+export const useCustomizeTitle = () => useAtomValue(customizeTitleAtom);
+
+/**
+ * Is Customized Logo Uploaded
+ */
+const isCustomizedLogoUploadedAtom = atom<boolean>(false);
+/**
+ * Is Customized Logo Uploaded atom setter
+ */
+export const useIsCustomizedLogoUploaded = () =>
+  useAtomValue(isCustomizedLogoUploadedAtom);
+
+/**
+ * GROWI Cloud URI atom
+ */
+const growiCloudUriAtom = atom<string | undefined>(undefined);
+/**
+ * GROWI Cloud URI atom getter
+ */
+export const growiCloudUriAtomGetter = atom((get) => get(growiCloudUriAtom));
+/**
+ * GROWI Cloud URI atom setter
+ */
+export const useGrowiCloudUri = () => useAtomValue(growiCloudUriAtom);
+
+/**
+ * GROWI Cloud App ID atom
+ */
+const growiAppIdForGrowiCloudAtom = atom<number | undefined>(undefined);
+/**
+ * GROWI Cloud App ID atom setter
+ */
+export const useGrowiAppIdForGrowiCloud = () =>
+  useAtomValue(growiAppIdForGrowiCloudAtom);
+
+/**
+ * Forced Color Scheme atom
+ */
+const forcedColorSchemeAtom = atom<ColorScheme | undefined>(undefined);
+/**
+ * Forced Color Scheme atom setter
+ */
+export const useForcedColorScheme = () => useAtomValue(forcedColorSchemeAtom);
+
+/**
+ * Audit Log Enabled atom
+ */
+const auditLogEnabledAtom = atom<boolean>(false);
+/**
+ * Audit Log Enabled atom setter
+ */
+export const useAuditLogEnabled = () => useAtomValue(auditLogEnabledAtom);
+
+/**
+ * Activity Expiration Seconds atom
+ */
+const activityExpirationSecondsAtom = atom<number>(0);
+/**
+ * Activity Expiration Seconds atom setter
+ */
+export const useActivityExpirationSeconds = () =>
+  useAtomValue(activityExpirationSecondsAtom);
+
+/**
+ * Audit Log Available Actions atom
+ */
+const auditLogAvailableActionsAtom = atom<SupportedActionType[]>([]);
+/**
+ * Audit Log Available Actions atom setter
+ */
+export const useAuditLogAvailableActions = () =>
+  useAtomValue(auditLogAvailableActionsAtom);
+
+export const _atomsForHydration = {
+  csrfTokenAtom,
+  currentPathnameAtom,
+  currentUserAtom,
+  appTitleAtom,
+  customTitleTemplateAtom,
+  siteUrlAtom,
+  siteUrlWithEmptyValueWarnAtom,
+  confidentialAtom,
+  growiVersionAtom,
+  isMaintenanceModeAtom,
+  isDefaultLogoAtom,
+  customizeTitleAtom,
+  isCustomizedLogoUploadedAtom,
+  growiCloudUriAtom,
+  growiAppIdForGrowiCloudAtom,
+  forcedColorSchemeAtom,
+  auditLogEnabledAtom,
+  activityExpirationSecondsAtom,
+  auditLogAvailableActionsAtom,
 };
 };

+ 50 - 17
apps/app/src/states/global/hydrate.ts

@@ -1,17 +1,23 @@
 import { useHydrateAtoms } from 'jotai/utils';
 import { useHydrateAtoms } from 'jotai/utils';
-import type { CommonInitialProps } from '~/pages/common-props';
-import {
+import type { CommonEachProps, CommonInitialProps } from '~/pages/common-props';
+import { _atomsForHydration } from './global';
+
+const {
   appTitleAtom,
   appTitleAtom,
   confidentialAtom,
   confidentialAtom,
+  csrfTokenAtom,
+  currentPathnameAtom,
+  currentUserAtom,
   customTitleTemplateAtom,
   customTitleTemplateAtom,
   forcedColorSchemeAtom,
   forcedColorSchemeAtom,
   growiAppIdForGrowiCloudAtom,
   growiAppIdForGrowiCloudAtom,
   growiCloudUriAtom,
   growiCloudUriAtom,
   growiVersionAtom,
   growiVersionAtom,
   isDefaultLogoAtom,
   isDefaultLogoAtom,
+  isMaintenanceModeAtom,
   siteUrlAtom,
   siteUrlAtom,
   siteUrlWithEmptyValueWarnAtom,
   siteUrlWithEmptyValueWarnAtom,
-} from './global';
+} = _atomsForHydration;
 
 
 /**
 /**
  * Hook for hydrating global UI state atoms with server-side data
  * Hook for hydrating global UI state atoms with server-side data
@@ -20,22 +26,49 @@ import {
  * @param commonInitialProps - Server-side common properties from getServerSideCommonInitialProps
  * @param commonInitialProps - Server-side common properties from getServerSideCommonInitialProps
  */
  */
 export const useHydrateGlobalInitialAtoms = (
 export const useHydrateGlobalInitialAtoms = (
-  commonInitialProps: CommonInitialProps,
+  commonInitialProps: CommonInitialProps | undefined,
+): void => {
+  // Hydrate global atoms with server-side data
+  useHydrateAtoms(
+    commonInitialProps == null
+      ? []
+      : [
+          [appTitleAtom, commonInitialProps.appTitle],
+          [siteUrlAtom, commonInitialProps.siteUrl],
+          [
+            siteUrlWithEmptyValueWarnAtom,
+            commonInitialProps.siteUrlWithEmptyValueWarn,
+          ],
+          [confidentialAtom, commonInitialProps.confidential],
+          [growiVersionAtom, commonInitialProps.growiVersion],
+          [isDefaultLogoAtom, commonInitialProps.isDefaultLogo],
+          [customTitleTemplateAtom, commonInitialProps.customTitleTemplate],
+          [growiCloudUriAtom, commonInitialProps.growiCloudUri],
+          [
+            growiAppIdForGrowiCloudAtom,
+            commonInitialProps.growiAppIdForGrowiCloud,
+          ],
+          [forcedColorSchemeAtom, commonInitialProps.forcedColorScheme],
+        ],
+  );
+};
+
+/**
+ * Hook for hydrating global UI state atoms with server-side data forcibly
+ * This should be called early in the app component to ensure atoms are properly initialized before rendering
+ * @param commonEachProps - Server-side common properties from getServerSideCommonEachProps
+ */
+export const useHydrateGlobalEachAtoms = (
+  commonEachProps: CommonEachProps,
 ): void => {
 ): void => {
   // Hydrate global atoms with server-side data
   // Hydrate global atoms with server-side data
-  useHydrateAtoms([
-    [appTitleAtom, commonInitialProps.appTitle],
-    [siteUrlAtom, commonInitialProps.siteUrl],
+  useHydrateAtoms(
     [
     [
-      siteUrlWithEmptyValueWarnAtom,
-      commonInitialProps.siteUrlWithEmptyValueWarn,
+      [csrfTokenAtom, commonEachProps.csrfToken],
+      [currentPathnameAtom, commonEachProps.currentPathname],
+      [currentUserAtom, commonEachProps.currentUser],
+      [isMaintenanceModeAtom, commonEachProps.isMaintenanceMode],
     ],
     ],
-    [confidentialAtom, commonInitialProps.confidential],
-    [growiVersionAtom, commonInitialProps.growiVersion],
-    [isDefaultLogoAtom, commonInitialProps.isDefaultLogo],
-    [customTitleTemplateAtom, commonInitialProps.customTitleTemplate],
-    [growiCloudUriAtom, commonInitialProps.growiCloudUri],
-    [growiAppIdForGrowiCloudAtom, commonInitialProps.growiAppIdForGrowiCloud],
-    [forcedColorSchemeAtom, commonInitialProps.forcedColorScheme],
-  ]);
+    { dangerouslyForceHydrate: true },
+  );
 };
 };

+ 0 - 11
apps/app/src/states/helper.ts

@@ -1,11 +0,0 @@
-import {
-  type Atom,
-  type PrimitiveAtom,
-  type SetStateAction,
-} from 'jotai';
-
-export type UseAtom<AtomType> = AtomType extends PrimitiveAtom<infer Value>
-  ? readonly [Value, (update: SetStateAction<Value>) => void]
-  : AtomType extends Atom<infer Value>
-  ? readonly [Value, never]
-  : never;

+ 24 - 56
apps/app/src/states/page/hooks.ts

@@ -1,8 +1,7 @@
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { pagePathUtils } from '@growi/core/dist/utils';
-import { useAtom } from 'jotai';
+import { useAtomValue } from 'jotai';
 
 
 import { useCurrentPathname } from '../global';
 import { useCurrentPathname } from '../global';
-import type { UseAtom } from '../helper';
 
 
 import {
 import {
   currentPageDataAtom,
   currentPageDataAtom,
@@ -28,60 +27,32 @@ import {
  */
  */
 
 
 // Read-only hooks for page state
 // Read-only hooks for page state
-export const useCurrentPageId = (): UseAtom<typeof currentPageIdAtom> => {
-  return useAtom(currentPageIdAtom);
-};
+export const useCurrentPageId = () => useAtomValue(currentPageIdAtom);
 
 
-export const useCurrentPageData = (): UseAtom<typeof currentPageDataAtom> => {
-  return useAtom(currentPageDataAtom);
-};
+export const useCurrentPageData = () => useAtomValue(currentPageDataAtom);
 
 
-export const usePageNotFound = (): UseAtom<typeof pageNotFoundAtom> => {
-  return useAtom(pageNotFoundAtom);
-};
+export const usePageNotFound = () => useAtomValue(pageNotFoundAtom);
 
 
-export const usePageNotCreatable = (): UseAtom<typeof pageNotCreatableAtom> => {
-  return useAtom(pageNotCreatableAtom);
-};
+export const usePageNotCreatable = () => useAtomValue(pageNotCreatableAtom);
 
 
-export const useLatestRevision = (): UseAtom<typeof latestRevisionAtom> => {
-  return useAtom(latestRevisionAtom);
-};
+export const useLatestRevision = () => useAtomValue(latestRevisionAtom);
 
 
-export const useShareLinkId = (): UseAtom<typeof shareLinkIdAtom> => {
-  return useAtom(shareLinkIdAtom);
-};
+export const useShareLinkId = () => useAtomValue(shareLinkIdAtom);
 
 
-export const useTemplateTags = (): UseAtom<typeof templateTagsAtom> => {
-  return useAtom(templateTagsAtom);
-};
+export const useTemplateTags = () => useAtomValue(templateTagsAtom);
 
 
-export const useTemplateBody = (): UseAtom<typeof templateBodyAtom> => {
-  return useAtom(templateBodyAtom);
-};
+export const useTemplateBody = () => useAtomValue(templateBodyAtom);
 
 
 // Remote revision hooks (replacements for stores/remote-latest-page.ts)
 // Remote revision hooks (replacements for stores/remote-latest-page.ts)
-export const useRemoteRevisionId = (): UseAtom<typeof remoteRevisionIdAtom> => {
-  return useAtom(remoteRevisionIdAtom);
-};
+export const useRemoteRevisionId = () => useAtomValue(remoteRevisionIdAtom);
 
 
-export const useRemoteRevisionBody = (): UseAtom<
-  typeof remoteRevisionBodyAtom
-> => {
-  return useAtom(remoteRevisionBodyAtom);
-};
+export const useRemoteRevisionBody = () => useAtomValue(remoteRevisionBodyAtom);
 
 
-export const useRemoteRevisionLastUpdateUser = (): UseAtom<
-  typeof remoteRevisionLastUpdateUserAtom
-> => {
-  return useAtom(remoteRevisionLastUpdateUserAtom);
-};
+export const useRemoteRevisionLastUpdateUser = () =>
+  useAtomValue(remoteRevisionLastUpdateUserAtom);
 
 
-export const useRemoteRevisionLastUpdatedAt = (): UseAtom<
-  typeof remoteRevisionLastUpdatedAtAtom
-> => {
-  return useAtom(remoteRevisionLastUpdatedAtAtom);
-};
+export const useRemoteRevisionLastUpdatedAt = () =>
+  useAtomValue(remoteRevisionLastUpdatedAtAtom);
 
 
 // Enhanced computed hooks (pure Jotai - no SWR needed)
 // Enhanced computed hooks (pure Jotai - no SWR needed)
 
 
@@ -89,31 +60,28 @@ export const useRemoteRevisionLastUpdatedAt = (): UseAtom<
  * Get current page path with fallback to pathname
  * Get current page path with fallback to pathname
  * Pure Jotai replacement for stores/page.tsx useCurrentPagePath
  * Pure Jotai replacement for stores/page.tsx useCurrentPagePath
  */
  */
-export const useCurrentPagePath = (): readonly [string | undefined] => {
-  const [currentPagePath] = useAtom(currentPagePathAtom);
-  const [currentPathname] = useCurrentPathname();
+export const useCurrentPagePath = (): string | undefined => {
+  const currentPagePath = useAtomValue(currentPagePathAtom);
+  const currentPathname = useCurrentPathname();
 
 
   if (currentPagePath != null) {
   if (currentPagePath != null) {
-    return [currentPagePath];
+    return currentPagePath;
   }
   }
   if (currentPathname != null && !pagePathUtils.isPermalink(currentPathname)) {
   if (currentPathname != null && !pagePathUtils.isPermalink(currentPathname)) {
-    return [currentPathname];
+    return currentPathname;
   }
   }
-  return [undefined];
+  return undefined;
 };
 };
 
 
 /**
 /**
  * Check if current page is in trash
  * Check if current page is in trash
  * Pure Jotai replacement for stores/page.tsx useIsTrashPage
  * Pure Jotai replacement for stores/page.tsx useIsTrashPage
  */
  */
-export const useIsTrashPage = (): readonly [boolean, never] => {
-  return useAtom(isTrashPageAtom);
-};
+export const useIsTrashPage = (): boolean => useAtomValue(isTrashPageAtom);
 
 
 /**
 /**
  * Check if current revision is outdated
  * Check if current revision is outdated
  * Pure Jotai replacement for stores/page.tsx useIsRevisionOutdated
  * Pure Jotai replacement for stores/page.tsx useIsRevisionOutdated
  */
  */
-export const useIsRevisionOutdated = (): readonly [boolean, never] => {
-  return useAtom(isRevisionOutdatedAtom);
-};
+export const useIsRevisionOutdated = (): boolean =>
+  useAtomValue(isRevisionOutdatedAtom);

+ 18 - 17
apps/app/src/states/page/index.ts

@@ -6,23 +6,24 @@
  */
  */
 
 
 // Core page state hooks
 // Core page state hooks
-export {
-  useCurrentPageData,
-  useCurrentPageId,
-  useCurrentPagePath,
-  useIsRevisionOutdated,
-  useIsTrashPage,
-  useLatestRevision,
-  usePageNotCreatable,
-  usePageNotFound,
-  useRemoteRevisionBody,
-  // Remote revision hooks (replacements for stores/remote-latest-page.ts)
-  useRemoteRevisionId,
-  useRemoteRevisionLastUpdatedAt,
-  useRemoteRevisionLastUpdateUser,
-  useTemplateBody,
-  useTemplateTags,
-} from './hooks';
+// export {
+//   useCurrentPageData,
+//   useCurrentPageId,
+//   useCurrentPagePath,
+//   useIsRevisionOutdated,
+//   useIsTrashPage,
+//   useLatestRevision,
+//   usePageNotCreatable,
+//   usePageNotFound,
+//   useRemoteRevisionBody,
+//   // Remote revision hooks (replacements for stores/remote-latest-page.ts)
+//   useRemoteRevisionId,
+//   useRemoteRevisionLastUpdatedAt,
+//   useRemoteRevisionLastUpdateUser,
+//   useTemplateBody,
+//   useTemplateTags,
+// } from './hooks';
+export * from './hooks';
 export { useCurrentPageLoading } from './use-current-page-loading';
 export { useCurrentPageLoading } from './use-current-page-loading';
 // Data fetching hooks
 // Data fetching hooks
 export { useFetchCurrentPage } from './use-fetch-current-page';
 export { useFetchCurrentPage } from './use-fetch-current-page';

+ 1 - 5
apps/app/src/states/page/redirect.ts

@@ -1,12 +1,8 @@
 import { atom, useAtom } from 'jotai';
 import { atom, useAtom } from 'jotai';
 
 
-import type { UseAtom } from '../helper';
-
 /**
 /**
  * Atom for redirect from path
  * Atom for redirect from path
  */
  */
 const redirectFromAtom = atom<string | null>(null);
 const redirectFromAtom = atom<string | null>(null);
 
 
-export const useRedirectFrom = (): UseAtom<typeof redirectFromAtom> => {
-  return useAtom(redirectFromAtom);
-};
+export const useRedirectFrom = () => useAtom(redirectFromAtom);

+ 1 - 1
apps/app/src/states/page/use-current-page-loading.ts

@@ -1,6 +1,6 @@
 import { useAtomValue } from 'jotai';
 import { useAtomValue } from 'jotai';
 
 
-import { pageLoadingAtom, pageErrorAtom } from './internal-atoms';
+import { pageErrorAtom, pageLoadingAtom } from './internal-atoms';
 
 
 /**
 /**
  * Hook to access current page loading state
  * Hook to access current page loading state

+ 84 - 28
apps/app/src/states/page/use-fetch-current-page.spec.tsx

@@ -1,10 +1,15 @@
 import type {
 import type {
-  IPagePopulatedToShowRevision, IRevisionHasId, IUserHasId, Lang, PageGrant, PageStatus,
+  IPagePopulatedToShowRevision,
+  IRevisionHasId,
+  IUserHasId,
+  Lang,
+  PageGrant,
+  PageStatus,
 } from '@growi/core';
 } from '@growi/core';
 import { renderHook, waitFor } from '@testing-library/react';
 import { renderHook, waitFor } from '@testing-library/react';
 // eslint-disable-next-line no-restricted-imports
 // eslint-disable-next-line no-restricted-imports
 import type { AxiosResponse } from 'axios';
 import type { AxiosResponse } from 'axios';
-import { Provider, createStore } from 'jotai';
+import { createStore, Provider } from 'jotai';
 import type { NextRouter } from 'next/router';
 import type { NextRouter } from 'next/router';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
 import { vi } from 'vitest';
 import { vi } from 'vitest';
@@ -14,7 +19,12 @@ import { mockDeep } from 'vitest-mock-extended';
 import * as apiv3Client from '~/client/util/apiv3-client';
 import * as apiv3Client from '~/client/util/apiv3-client';
 import { useFetchCurrentPage } from '~/states/page';
 import { useFetchCurrentPage } from '~/states/page';
 import {
 import {
-  currentPageDataAtom, currentPageIdAtom, pageErrorAtom, pageLoadingAtom, pageNotCreatableAtom, pageNotFoundAtom,
+  currentPageDataAtom,
+  currentPageIdAtom,
+  pageErrorAtom,
+  pageLoadingAtom,
+  pageNotCreatableAtom,
+  pageNotFoundAtom,
 } from '~/states/page/internal-atoms';
 } from '~/states/page/internal-atoms';
 
 
 // Mock Next.js router
 // Mock Next.js router
@@ -47,7 +57,11 @@ const mockUser: IUserHasId = {
 
 
 // This is a minimal mock to satisfy the IPagePopulatedToShowRevision type.
 // This is a minimal mock to satisfy the IPagePopulatedToShowRevision type.
 // It is based on the type definition in packages/core/src/interfaces/page.ts
 // It is based on the type definition in packages/core/src/interfaces/page.ts
-const createPageDataMock = (pageId: string, path: string, body: string): IPagePopulatedToShowRevision => {
+const createPageDataMock = (
+  pageId: string,
+  path: string,
+  body: string,
+): IPagePopulatedToShowRevision => {
   const revision: IRevisionHasId = {
   const revision: IRevisionHasId = {
     _id: `rev_${pageId}`,
     _id: `rev_${pageId}`,
     pageId,
     pageId,
@@ -86,9 +100,7 @@ const createPageDataMock = (pageId: string, path: string, body: string): IPagePo
   };
   };
 };
 };
 
 
-
 describe('useFetchCurrentPage - Integration Test', () => {
 describe('useFetchCurrentPage - Integration Test', () => {
-
   let store: ReturnType<typeof createStore>;
   let store: ReturnType<typeof createStore>;
 
 
   // Helper to render the hook with Jotai provider
   // Helper to render the hook with Jotai provider
@@ -98,7 +110,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
     });
     });
   };
   };
 
 
-  const mockApiResponse = (page: IPagePopulatedToShowRevision): AxiosResponse<{ page: IPagePopulatedToShowRevision }> => {
+  const mockApiResponse = (
+    page: IPagePopulatedToShowRevision,
+  ): AxiosResponse<{ page: IPagePopulatedToShowRevision }> => {
     return {
     return {
       data: { page },
       data: { page },
       status: 200,
       status: 200,
@@ -118,18 +132,30 @@ describe('useFetchCurrentPage - Integration Test', () => {
     (useRouter as ReturnType<typeof vi.fn>).mockReturnValue(mockRouter);
     (useRouter as ReturnType<typeof vi.fn>).mockReturnValue(mockRouter);
 
 
     // Default API response
     // Default API response
-    const defaultPageData = createPageDataMock('defaultPageId', '/initial/path', 'default content');
+    const defaultPageData = createPageDataMock(
+      'defaultPageId',
+      '/initial/path',
+      'default content',
+    );
     mockedApiv3Get.mockResolvedValue(mockApiResponse(defaultPageData));
     mockedApiv3Get.mockResolvedValue(mockApiResponse(defaultPageData));
   });
   });
 
 
-  it('should fetch data and update atoms when called with a new path', async() => {
+  it('should fetch data and update atoms when called with a new path', async () => {
     // Arrange: Start at an initial page
     // Arrange: Start at an initial page
-    const initialPageData = createPageDataMock('initialPageId', '/initial/path', 'initial content');
+    const initialPageData = createPageDataMock(
+      'initialPageId',
+      '/initial/path',
+      'initial content',
+    );
     store.set(currentPageIdAtom, initialPageData._id);
     store.set(currentPageIdAtom, initialPageData._id);
     store.set(currentPageDataAtom, initialPageData);
     store.set(currentPageDataAtom, initialPageData);
 
 
     // Arrange: Navigate to a new page
     // Arrange: Navigate to a new page
-    const newPageData = createPageDataMock('newPageId', '/new/page', 'new content');
+    const newPageData = createPageDataMock(
+      'newPageId',
+      '/new/page',
+      'new content',
+    );
     mockedApiv3Get.mockResolvedValue(mockApiResponse(newPageData));
     mockedApiv3Get.mockResolvedValue(mockApiResponse(newPageData));
 
 
     // Act
     // Act
@@ -139,7 +165,10 @@ describe('useFetchCurrentPage - Integration Test', () => {
     // Assert: Wait for state updates
     // Assert: Wait for state updates
     await waitFor(() => {
     await waitFor(() => {
       // 1. API was called correctly
       // 1. API was called correctly
-      expect(mockedApiv3Get).toHaveBeenCalledWith('/page', expect.objectContaining({ path: '/new/page' }));
+      expect(mockedApiv3Get).toHaveBeenCalledWith(
+        '/page',
+        expect.objectContaining({ path: '/new/page' }),
+      );
 
 
       // 2. Atoms were updated
       // 2. Atoms were updated
       expect(store.get(currentPageIdAtom)).toBe(newPageData._id);
       expect(store.get(currentPageIdAtom)).toBe(newPageData._id);
@@ -150,9 +179,13 @@ describe('useFetchCurrentPage - Integration Test', () => {
     });
     });
   });
   });
 
 
-  it('should not re-fetch if target path is the same as current', async() => {
+  it('should not re-fetch if target path is the same as current', async () => {
     // Arrange: Current state is set
     // Arrange: Current state is set
-    const currentPageData = createPageDataMock('page1', '/same/path', 'current content');
+    const currentPageData = createPageDataMock(
+      'page1',
+      '/same/path',
+      'current content',
+    );
     store.set(currentPageIdAtom, currentPageData._id);
     store.set(currentPageIdAtom, currentPageData._id);
     store.set(currentPageDataAtom, currentPageData);
     store.set(currentPageDataAtom, currentPageData);
 
 
@@ -162,14 +195,17 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
 
     // Assert
     // Assert
     // Use a short timeout to ensure no fetch is initiated
     // Use a short timeout to ensure no fetch is initiated
-    await new Promise(resolve => setTimeout(resolve, 100));
+    await new Promise((resolve) => setTimeout(resolve, 100));
     expect(mockedApiv3Get).not.toHaveBeenCalled();
     expect(mockedApiv3Get).not.toHaveBeenCalled();
   });
   });
 
 
-
-  it('should handle fetching the root page', async() => {
+  it('should handle fetching the root page', async () => {
     // Arrange: Start on a regular page
     // Arrange: Start on a regular page
-    const regularPageData = createPageDataMock('regularPageId', '/some/page', 'Regular page content');
+    const regularPageData = createPageDataMock(
+      'regularPageId',
+      '/some/page',
+      'Regular page content',
+    );
     mockedApiv3Get.mockResolvedValue(mockApiResponse(regularPageData));
     mockedApiv3Get.mockResolvedValue(mockApiResponse(regularPageData));
 
 
     const { result } = renderHookWithProvider();
     const { result } = renderHookWithProvider();
@@ -181,7 +217,11 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
 
     // Arrange: Navigate to the root page
     // Arrange: Navigate to the root page
     mockedApiv3Get.mockClear();
     mockedApiv3Get.mockClear();
-    const rootPageData = createPageDataMock('rootPageId', '/', 'Root page content');
+    const rootPageData = createPageDataMock(
+      'rootPageId',
+      '/',
+      'Root page content',
+    );
     mockedApiv3Get.mockResolvedValue(mockApiResponse(rootPageData));
     mockedApiv3Get.mockResolvedValue(mockApiResponse(rootPageData));
 
 
     // Act
     // Act
@@ -189,16 +229,23 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
 
     // Assert: Navigation to root works
     // Assert: Navigation to root works
     await waitFor(() => {
     await waitFor(() => {
-      expect(mockedApiv3Get).toHaveBeenCalledWith('/page', expect.objectContaining({ path: '/' }));
+      expect(mockedApiv3Get).toHaveBeenCalledWith(
+        '/page',
+        expect.objectContaining({ path: '/' }),
+      );
       expect(store.get(currentPageIdAtom)).toBe('rootPageId');
       expect(store.get(currentPageIdAtom)).toBe('rootPageId');
     });
     });
   });
   });
 
 
-  it('should handle encoded URI path', async() => {
+  it('should handle encoded URI path', async () => {
     // Arrange
     // Arrange
     const encodedPath = '/encoded%2Fpath'; // /encoded/path
     const encodedPath = '/encoded%2Fpath'; // /encoded/path
     const decodedPath = decodeURIComponent(encodedPath);
     const decodedPath = decodeURIComponent(encodedPath);
-    const newPageData = createPageDataMock('encodedPageId', decodedPath, 'encoded content');
+    const newPageData = createPageDataMock(
+      'encodedPageId',
+      decodedPath,
+      'encoded content',
+    );
     mockedApiv3Get.mockResolvedValue(mockApiResponse(newPageData));
     mockedApiv3Get.mockResolvedValue(mockApiResponse(newPageData));
 
 
     // Act
     // Act
@@ -207,15 +254,22 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
 
     // Assert
     // Assert
     await waitFor(() => {
     await waitFor(() => {
-      expect(mockedApiv3Get).toHaveBeenCalledWith('/page', expect.objectContaining({ path: decodedPath }));
+      expect(mockedApiv3Get).toHaveBeenCalledWith(
+        '/page',
+        expect.objectContaining({ path: decodedPath }),
+      );
       expect(store.get(currentPageIdAtom)).toBe('encodedPageId');
       expect(store.get(currentPageIdAtom)).toBe('encodedPageId');
     });
     });
   });
   });
 
 
-  it('should handle permalink path', async() => {
+  it('should handle permalink path', async () => {
     // Arrange
     // Arrange
     const permalink = '/65d4e0a0f7b7b2e5a8652e86';
     const permalink = '/65d4e0a0f7b7b2e5a8652e86';
-    const newPageData = createPageDataMock('65d4e0a0f7b7b2e5a8652e86', '/any/path', 'permalink content');
+    const newPageData = createPageDataMock(
+      '65d4e0a0f7b7b2e5a8652e86',
+      '/any/path',
+      'permalink content',
+    );
     mockedApiv3Get.mockResolvedValue(mockApiResponse(newPageData));
     mockedApiv3Get.mockResolvedValue(mockApiResponse(newPageData));
 
 
     // Act
     // Act
@@ -224,12 +278,15 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
 
     // Assert
     // Assert
     await waitFor(() => {
     await waitFor(() => {
-      expect(mockedApiv3Get).toHaveBeenCalledWith('/page', expect.objectContaining({ pageId: '65d4e0a0f7b7b2e5a8652e86' }));
+      expect(mockedApiv3Get).toHaveBeenCalledWith(
+        '/page',
+        expect.objectContaining({ pageId: '65d4e0a0f7b7b2e5a8652e86' }),
+      );
       expect(store.get(currentPageIdAtom)).toBe('65d4e0a0f7b7b2e5a8652e86');
       expect(store.get(currentPageIdAtom)).toBe('65d4e0a0f7b7b2e5a8652e86');
     });
     });
   });
   });
 
 
-  it('should set pageNotFoundAtom and pageNotCreatableAtom on 404 error for a non-creatable path', async() => {
+  it('should set pageNotFoundAtom and pageNotCreatableAtom on 404 error for a non-creatable path', async () => {
     // Arrange
     // Arrange
     const notCreatablePath = '/user';
     const notCreatablePath = '/user';
     const apiError = {
     const apiError = {
@@ -250,5 +307,4 @@ describe('useFetchCurrentPage - Integration Test', () => {
       expect(store.get(pageErrorAtom)).toEqual(apiError);
       expect(store.get(pageErrorAtom)).toEqual(apiError);
     });
     });
   });
   });
-
 });
 });

+ 1 - 1
apps/app/src/states/page/use-page-loading.ts

@@ -1,6 +1,6 @@
 import { useAtomValue } from 'jotai';
 import { useAtomValue } from 'jotai';
 
 
-import { pageLoadingAtom, pageErrorAtom } from './internal-atoms';
+import { pageErrorAtom, pageLoadingAtom } from './internal-atoms';
 
 
 /**
 /**
  * Hook to access current page loading state
  * Hook to access current page loading state

+ 2 - 160
apps/app/src/states/server-configurations/server-configurations.ts

@@ -1,303 +1,147 @@
-import { atom, useAtom } from 'jotai';
+import { atom, useAtomValue } from 'jotai';
 
 
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 
 
-import type { UseAtom } from '../helper';
-
 /**
 /**
  * Atom for AI feature enabled status
  * Atom for AI feature enabled status
  */
  */
 export const aiEnabledAtom = atom<boolean>(false);
 export const aiEnabledAtom = atom<boolean>(false);
 
 
-export const useIsAiEnabled = (): UseAtom<typeof aiEnabledAtom> => {
-  return useAtom(aiEnabledAtom);
-};
-
 /**
 /**
  * Atom for limit learnable page count per assistant
  * Atom for limit learnable page count per assistant
  */
  */
 export const limitLearnablePageCountPerAssistantAtom = atom<number>(0);
 export const limitLearnablePageCountPerAssistantAtom = atom<number>(0);
 
 
-export const useLimitLearnablePageCountPerAssistant = (): UseAtom<
-  typeof limitLearnablePageCountPerAssistantAtom
-> => {
-  return useAtom(limitLearnablePageCountPerAssistantAtom);
-};
-
 /**
 /**
  * Atom for users homepage deletion enabled status
  * Atom for users homepage deletion enabled status
  */
  */
 export const isUsersHomepageDeletionEnabledAtom = atom<boolean>(false);
 export const isUsersHomepageDeletionEnabledAtom = atom<boolean>(false);
 
 
-export const useIsUsersHomepageDeletionEnabled = (): UseAtom<
-  typeof isUsersHomepageDeletionEnabledAtom
-> => {
-  return useAtom(isUsersHomepageDeletionEnabledAtom);
-};
-
 /**
 /**
  * Atom for default indent size (default indent size)
  * Atom for default indent size (default indent size)
  */
  */
 export const defaultIndentSizeAtom = atom<number>(4);
 export const defaultIndentSizeAtom = atom<number>(4);
 
 
-export const useDefaultIndentSize = (): UseAtom<
-  typeof defaultIndentSizeAtom
-> => {
-  return useAtom(defaultIndentSizeAtom);
-};
-
 /**
 /**
  * Atom for mailer setup status
  * Atom for mailer setup status
  */
  */
 export const isMailerSetupAtom = atom<boolean>(false);
 export const isMailerSetupAtom = atom<boolean>(false);
 
 
-export const useIsMailerSetup = (): UseAtom<typeof isMailerSetupAtom> => {
-  return useAtom(isMailerSetupAtom);
-};
-
 /**
 /**
  * Atom for search scope children as default
  * Atom for search scope children as default
  */
  */
 export const isSearchScopeChildrenAsDefaultAtom = atom<boolean>(false);
 export const isSearchScopeChildrenAsDefaultAtom = atom<boolean>(false);
 
 
-export const useIsSearchScopeChildrenAsDefault = (): UseAtom<
-  typeof isSearchScopeChildrenAsDefaultAtom
-> => {
-  return useAtom(isSearchScopeChildrenAsDefaultAtom);
-};
-
 /**
 /**
  * Atom for elasticsearch max body length to index
  * Atom for elasticsearch max body length to index
  */
  */
 export const elasticsearchMaxBodyLengthToIndexAtom = atom<number>(0);
 export const elasticsearchMaxBodyLengthToIndexAtom = atom<number>(0);
 
 
-export const useElasticsearchMaxBodyLengthToIndex = (): UseAtom<
-  typeof elasticsearchMaxBodyLengthToIndexAtom
-> => {
-  return useAtom(elasticsearchMaxBodyLengthToIndexAtom);
-};
-
 /**
 /**
  * Atom for ROM user allowed to comment
  * Atom for ROM user allowed to comment
  */
  */
 export const isRomUserAllowedToCommentAtom = atom<boolean>(false);
 export const isRomUserAllowedToCommentAtom = atom<boolean>(false);
 
 
-export const useIsRomUserAllowedToComment = (): UseAtom<
-  typeof isRomUserAllowedToCommentAtom
-> => {
-  return useAtom(isRomUserAllowedToCommentAtom);
-};
-
 /**
 /**
  * Atom for drawio URI
  * Atom for drawio URI
  */
  */
 export const drawioUriAtom = atom<string | null>(null);
 export const drawioUriAtom = atom<string | null>(null);
 
 
-export const useDrawioUri = (): UseAtom<typeof drawioUriAtom> => {
-  return useAtom(drawioUriAtom);
-};
-
 /**
 /**
  * Atom for all reply shown
  * Atom for all reply shown
  */
  */
 export const isAllReplyShownAtom = atom<boolean>(false);
 export const isAllReplyShownAtom = atom<boolean>(false);
 
 
-export const useIsAllReplyShown = (): UseAtom<typeof isAllReplyShownAtom> => {
-  return useAtom(isAllReplyShownAtom);
-};
-
 /**
 /**
  * Atom for show page limitation L
  * Atom for show page limitation L
  */
  */
 export const showPageLimitationLAtom = atom<number>(50);
 export const showPageLimitationLAtom = atom<number>(50);
 
 
-export const useShowPageLimitationL = (): UseAtom<
-  typeof showPageLimitationLAtom
-> => {
-  return useAtom(showPageLimitationLAtom);
-};
-
 /**
 /**
  * Atom for show page limitation XL
  * Atom for show page limitation XL
  */
  */
 export const showPageLimitationXLAtom = atom<number>(20);
 export const showPageLimitationXLAtom = atom<number>(20);
 
 
-export const useShowPageLimitationXL = (): UseAtom<
-  typeof showPageLimitationXLAtom
-> => {
-  return useAtom(showPageLimitationXLAtom);
-};
-
 /**
 /**
  * Atom for show page side authors
  * Atom for show page side authors
  */
  */
 export const showPageSideAuthorsAtom = atom<boolean>(false);
 export const showPageSideAuthorsAtom = atom<boolean>(false);
 
 
-export const useShowPageSideAuthors = (): UseAtom<
-  typeof showPageSideAuthorsAtom
-> => {
-  return useAtom(showPageSideAuthorsAtom);
-};
-
 /**
 /**
  * Atom for container fluid
  * Atom for container fluid
  */
  */
 export const isContainerFluidAtom = atom<boolean>(false);
 export const isContainerFluidAtom = atom<boolean>(false);
 
 
-export const useIsContainerFluid = (): UseAtom<typeof isContainerFluidAtom> => {
-  return useAtom(isContainerFluidAtom);
-};
-
 /**
 /**
  * Atom for stale notification enabled
  * Atom for stale notification enabled
  */
  */
 export const isEnabledStaleNotificationAtom = atom<boolean>(false);
 export const isEnabledStaleNotificationAtom = atom<boolean>(false);
 
 
-export const useIsEnabledStaleNotification = (): UseAtom<
-  typeof isEnabledStaleNotificationAtom
-> => {
-  return useAtom(isEnabledStaleNotificationAtom);
-};
-
 /**
 /**
  * Atom for disable link sharing
  * Atom for disable link sharing
  */
  */
 export const disableLinkSharingAtom = atom<boolean>(false);
 export const disableLinkSharingAtom = atom<boolean>(false);
 
 
-export const useDisableLinkSharing = (): UseAtom<
-  typeof disableLinkSharingAtom
-> => {
-  return useAtom(disableLinkSharingAtom);
-};
-
 /**
 /**
  * Atom for indent size forced
  * Atom for indent size forced
  */
  */
 export const isIndentSizeForcedAtom = atom<boolean>(false);
 export const isIndentSizeForcedAtom = atom<boolean>(false);
 
 
-export const useIsIndentSizeForced = (): UseAtom<
-  typeof isIndentSizeForcedAtom
-> => {
-  return useAtom(isIndentSizeForcedAtom);
-};
-
 /**
 /**
  * Atom for attach title header enabled
  * Atom for attach title header enabled
  */
  */
 export const isEnabledAttachTitleHeaderAtom = atom<boolean>(false);
 export const isEnabledAttachTitleHeaderAtom = atom<boolean>(false);
 
 
-export const useIsEnabledAttachTitleHeader = (): UseAtom<
-  typeof isEnabledAttachTitleHeaderAtom
-> => {
-  return useAtom(isEnabledAttachTitleHeaderAtom);
-};
-
 /**
 /**
  * Atom for search service configured
  * Atom for search service configured
  */
  */
 export const isSearchServiceConfiguredAtom = atom<boolean>(false);
 export const isSearchServiceConfiguredAtom = atom<boolean>(false);
 
 
-export const useIsSearchServiceConfigured = (): UseAtom<
-  typeof isSearchServiceConfiguredAtom
-> => {
-  return useAtom(isSearchServiceConfiguredAtom);
-};
-
 /**
 /**
  * Atom for search service reachable
  * Atom for search service reachable
  */
  */
 export const isSearchServiceReachableAtom = atom<boolean>(false);
 export const isSearchServiceReachableAtom = atom<boolean>(false);
 
 
-export const useIsSearchServiceReachable = (): UseAtom<
-  typeof isSearchServiceReachableAtom
-> => {
-  return useAtom(isSearchServiceReachableAtom);
-};
-
 /**
 /**
  * Atom for Slack configured
  * Atom for Slack configured
  */
  */
 export const isSlackConfiguredAtom = atom<boolean>(false);
 export const isSlackConfiguredAtom = atom<boolean>(false);
 
 
-export const useIsSlackConfigured = (): UseAtom<
-  typeof isSlackConfiguredAtom
-> => {
-  return useAtom(isSlackConfiguredAtom);
-};
-
 /**
 /**
  * Atom for ACL enabled
  * Atom for ACL enabled
  */
  */
 export const isAclEnabledAtom = atom<boolean>(false);
 export const isAclEnabledAtom = atom<boolean>(false);
 
 
-export const useIsAclEnabled = (): UseAtom<typeof isAclEnabledAtom> => {
-  return useAtom(isAclEnabledAtom);
-};
-
 /**
 /**
  * Atom for registration whitelist
  * Atom for registration whitelist
  */
  */
 export const registrationWhitelistAtom = atom<string[] | null>(null);
 export const registrationWhitelistAtom = atom<string[] | null>(null);
 
 
-export const useRegistrationWhitelist = (): UseAtom<
-  typeof registrationWhitelistAtom
-> => {
-  return useAtom(registrationWhitelistAtom);
-};
-
 /**
 /**
  * Atom for upload all file allowed
  * Atom for upload all file allowed
  */
  */
 export const isUploadAllFileAllowedAtom = atom<boolean>(false);
 export const isUploadAllFileAllowedAtom = atom<boolean>(false);
 
 
-export const useIsUploadAllFileAllowed = (): UseAtom<
-  typeof isUploadAllFileAllowedAtom
-> => {
-  return useAtom(isUploadAllFileAllowedAtom);
-};
-
 /**
 /**
  * Atom for upload enabled
  * Atom for upload enabled
  */
  */
 export const isUploadEnabledAtom = atom<boolean>(false);
 export const isUploadEnabledAtom = atom<boolean>(false);
 
 
-export const useIsUploadEnabled = (): UseAtom<typeof isUploadEnabledAtom> => {
-  return useAtom(isUploadEnabledAtom);
-};
-
 /**
 /**
  * Atom for bulk export pages enabled
  * Atom for bulk export pages enabled
  */
  */
 export const isBulkExportPagesEnabledAtom = atom<boolean>(false);
 export const isBulkExportPagesEnabledAtom = atom<boolean>(false);
 
 
-export const useIsBulkExportPagesEnabled = (): UseAtom<
-  typeof isBulkExportPagesEnabledAtom
-> => {
-  return useAtom(isBulkExportPagesEnabledAtom);
-};
-
 /**
 /**
  * Atom for PDF bulk export enabled
  * Atom for PDF bulk export enabled
  */
  */
 export const isPdfBulkExportEnabledAtom = atom<boolean>(false);
 export const isPdfBulkExportEnabledAtom = atom<boolean>(false);
 
 
-export const useIsPdfBulkExportEnabled = (): UseAtom<
-  typeof isPdfBulkExportEnabledAtom
-> => {
-  return useAtom(isPdfBulkExportEnabledAtom);
-};
-
 /**
 /**
  * Atom for local account registration enabled
  * Atom for local account registration enabled
  */
  */
 export const isLocalAccountRegistrationEnabledAtom = atom<boolean>(false);
 export const isLocalAccountRegistrationEnabledAtom = atom<boolean>(false);
 
 
-export const useIsLocalAccountRegistrationEnabled = (): UseAtom<
-  typeof isLocalAccountRegistrationEnabledAtom
-> => {
-  return useAtom(isLocalAccountRegistrationEnabledAtom);
-};
-
 /**
 /**
  * Atom for renderer config
  * Atom for renderer config
  */
  */
@@ -316,6 +160,4 @@ export const rendererConfigAtom = atom<RendererConfig>({
   customAttrWhitelist: {},
   customAttrWhitelist: {},
 });
 });
 
 
-export const useRendererConfig = (): UseAtom<typeof rendererConfigAtom> => {
-  return useAtom(rendererConfigAtom);
-};
+export const useRendererConfig = () => useAtomValue(rendererConfigAtom);

+ 2 - 4
apps/app/src/states/socket-io/socket-io.ts

@@ -15,9 +15,7 @@ const globalSocketAtom = atom<Socket | null>(null);
 /**
 /**
  * Hook to get WebSocket connection
  * Hook to get WebSocket connection
  */
  */
-export const useGlobalSocket = (): Socket | null => {
-  return useAtomValue(globalSocketAtom);
-};
+export const useGlobalSocket = () => useAtomValue(globalSocketAtom);
 
 
 /**
 /**
  * Hook to initialize WebSocket connection
  * Hook to initialize WebSocket connection
@@ -63,7 +61,7 @@ export const useSetupGlobalSocket = (): void => {
  */
  */
 export const useSetupGlobalSocketForPage = (): void => {
 export const useSetupGlobalSocketForPage = (): void => {
   const socket = useAtomValue(globalSocketAtom);
   const socket = useAtomValue(globalSocketAtom);
-  const [pageId] = useCurrentPageId();
+  const pageId = useCurrentPageId();
 
 
   useEffect(() => {
   useEffect(() => {
     if (socket == null || pageId == null) {
     if (socket == null || pageId == null) {

+ 7 - 8
apps/app/src/states/ui/device.ts

@@ -1,22 +1,21 @@
-import { useEffect } from 'react';
-
 import { isClient } from '@growi/core/dist/utils';
 import { isClient } from '@growi/core/dist/utils';
 import { Breakpoint } from '@growi/ui/dist/interfaces';
 import { Breakpoint } from '@growi/ui/dist/interfaces';
-import { addBreakpointListener, cleanupBreakpointListener } from '@growi/ui/dist/utils';
+import {
+  addBreakpointListener,
+  cleanupBreakpointListener,
+} from '@growi/ui/dist/utils';
 import { atom, useAtom } from 'jotai';
 import { atom, useAtom } from 'jotai';
-
-import type { UseAtom } from '../helper';
-
+import { useEffect } from 'react';
 
 
 // Device state atoms
 // Device state atoms
 export const isDeviceLargerThanXlAtom = atom(false);
 export const isDeviceLargerThanXlAtom = atom(false);
 
 
-export const useDeviceLargerThanXl = (): UseAtom<typeof isDeviceLargerThanXlAtom> => {
+export const useDeviceLargerThanXl = () => {
   const [isLargerThanXl, setIsLargerThanXl] = useAtom(isDeviceLargerThanXlAtom);
   const [isLargerThanXl, setIsLargerThanXl] = useAtom(isDeviceLargerThanXlAtom);
 
 
   useEffect(() => {
   useEffect(() => {
     if (isClient()) {
     if (isClient()) {
-      const xlOrAboveHandler = function(this: MediaQueryList): void {
+      const xlOrAboveHandler = function (this: MediaQueryList): void {
         // lg -> xl: matches will be true
         // lg -> xl: matches will be true
         // xl -> lg: matches will be false
         // xl -> lg: matches will be false
         setIsLargerThanXl(this.matches);
         setIsLargerThanXl(this.matches);

+ 1 - 1
apps/app/src/states/ui/editor/atoms.ts

@@ -5,7 +5,7 @@ import { EditorMode, EditorModeHash } from './types';
 import { determineEditorModeByHash } from './utils';
 import { determineEditorModeByHash } from './utils';
 
 
 // Base atom for editor mode
 // Base atom for editor mode
-export const editorModeBaseAtom = atom<EditorMode | null>(null);
+const editorModeBaseAtom = atom<EditorMode | null>(null);
 
 
 // Derived atom with initialization logic
 // Derived atom with initialization logic
 export const editorModeAtom = atom(
 export const editorModeAtom = atom(

+ 3 - 5
apps/app/src/states/ui/editor/hooks.ts

@@ -2,15 +2,14 @@ import { useAtom } from 'jotai';
 import { useCallback } from 'react';
 import { useCallback } from 'react';
 
 
 import { useIsEditable } from '~/states/context';
 import { useIsEditable } from '~/states/context';
-import type { UseAtom } from '~/states/helper';
 import { usePageNotFound } from '~/states/page';
 import { usePageNotFound } from '~/states/page';
 
 
 import { editingMarkdownAtom, editorModeAtom } from './atoms';
 import { editingMarkdownAtom, editorModeAtom } from './atoms';
 import { EditorMode, type UseEditorModeReturn } from './types';
 import { EditorMode, type UseEditorModeReturn } from './types';
 
 
 export const useEditorMode = (): UseEditorModeReturn => {
 export const useEditorMode = (): UseEditorModeReturn => {
-  const [isEditable] = useIsEditable();
-  const [isNotFound] = usePageNotFound();
+  const isEditable = useIsEditable();
+  const isNotFound = usePageNotFound();
   const [editorMode, setEditorModeRaw] = useAtom(editorModeAtom);
   const [editorMode, setEditorModeRaw] = useAtom(editorModeAtom);
 
 
   // Check if editor mode should be prevented
   // Check if editor mode should be prevented
@@ -47,5 +46,4 @@ export const useEditorMode = (): UseEditorModeReturn => {
   };
   };
 };
 };
 
 
-export const useEditingMarkdown = (): UseAtom<typeof editingMarkdownAtom> =>
-  useAtom(editingMarkdownAtom);
+export const useEditingMarkdown = () => useAtom(editingMarkdownAtom);

+ 4 - 3
apps/app/src/states/ui/editor/index.ts

@@ -1,8 +1,9 @@
 // Export only the essential public API
 // Export only the essential public API
-export { EditorMode } from './types';
-export type { EditorMode as EditorModeType } from './types';
-export { useEditorMode, useEditingMarkdown } from './hooks';
+
 export { editingMarkdownAtom } from './atoms';
 export { editingMarkdownAtom } from './atoms';
+export { useEditingMarkdown, useEditorMode } from './hooks';
+export type { EditorMode as EditorModeType } from './types';
+export { EditorMode } from './types';
 
 
 // Export utility functions that might be needed elsewhere
 // Export utility functions that might be needed elsewhere
 export { determineEditorModeByHash } from './utils';
 export { determineEditorModeByHash } from './utils';

+ 5 - 14
apps/app/src/states/ui/sidebar/sidebar.ts

@@ -2,14 +2,13 @@ import { atom, useAtom } from 'jotai';
 
 
 import { scheduleToPut } from '~/client/services/user-ui-settings';
 import { scheduleToPut } from '~/client/services/user-ui-settings';
 import { SidebarContentsType, SidebarMode } from '~/interfaces/ui';
 import { SidebarContentsType, SidebarMode } from '~/interfaces/ui';
-import type { UseAtom } from '../../helper';
 import { isDeviceLargerThanXlAtom } from '../device';
 import { isDeviceLargerThanXlAtom } from '../device';
 import { EditorMode } from '../editor';
 import { EditorMode } from '../editor';
 import { editorModeAtom } from '../editor/atoms'; // import the atom directly
 import { editorModeAtom } from '../editor/atoms'; // import the atom directly
 
 
 const isDrawerOpenedAtom = atom(false);
 const isDrawerOpenedAtom = atom(false);
 
 
-export const useDrawerOpened = (): UseAtom<typeof isDrawerOpenedAtom> => {
+export const useDrawerOpened = () => {
   return useAtom(isDrawerOpenedAtom);
   return useAtom(isDrawerOpenedAtom);
 };
 };
 
 
@@ -23,18 +22,14 @@ const preferCollapsedModeAtomExt = atom(
   },
   },
 );
 );
 
 
-export const usePreferCollapsedMode = (): UseAtom<
-  typeof preferCollapsedModeAtom
-> => {
+export const usePreferCollapsedMode = () => {
   return useAtom(preferCollapsedModeAtomExt);
   return useAtom(preferCollapsedModeAtomExt);
 };
 };
 
 
 // Collapsed contents opened state (temporary UI state, no persistence needed)
 // Collapsed contents opened state (temporary UI state, no persistence needed)
 const isCollapsedContentsOpenedAtom = atom(false);
 const isCollapsedContentsOpenedAtom = atom(false);
 
 
-export const useCollapsedContentsOpened = (): UseAtom<
-  typeof isCollapsedContentsOpenedAtom
-> => {
+export const useCollapsedContentsOpened = () => {
   return useAtom(isCollapsedContentsOpenedAtom);
   return useAtom(isCollapsedContentsOpenedAtom);
 };
 };
 
 
@@ -50,9 +45,7 @@ const currentSidebarContentsAtomExt = atom(
   },
   },
 );
 );
 
 
-export const useCurrentSidebarContents = (): UseAtom<
-  typeof currentSidebarContentsAtom
-> => {
+export const useCurrentSidebarContents = () => {
   return useAtom(currentSidebarContentsAtomExt);
   return useAtom(currentSidebarContentsAtomExt);
 };
 };
 
 
@@ -66,9 +59,7 @@ const currentProductNavWidthAtomExt = atom(
   },
   },
 );
 );
 
 
-export const useCurrentProductNavWidth = (): UseAtom<
-  typeof currentProductNavWidthAtom
-> => {
+export const useCurrentProductNavWidth = () => {
   return useAtom(currentProductNavWidthAtomExt);
   return useAtom(currentProductNavWidthAtomExt);
 };
 };
 
 

+ 5 - 4
apps/app/src/stores-universal/context.tsx

@@ -1,13 +1,14 @@
 import { AcceptedUploadFileType } from '@growi/core';
 import { AcceptedUploadFileType } from '@growi/core';
 import { useSWRStatic } from '@growi/core/dist/swr';
 import { useSWRStatic } from '@growi/core/dist/swr';
 import type EventEmitter from 'events';
 import type EventEmitter from 'events';
+import { useAtomValue } from 'jotai';
 import type { SWRResponse } from 'swr';
 import type { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 import useSWRImmutable from 'swr/immutable';
 
 
 import type { SupportedActionType } from '~/interfaces/activity';
 import type { SupportedActionType } from '~/interfaces/activity';
 import {
 import {
-  useIsUploadAllFileAllowed,
-  useIsUploadEnabled,
+  isUploadAllFileAllowedAtom,
+  isUploadEnabledAtom,
 } from '~/states/server-configurations';
 } from '~/states/server-configurations';
 
 
 import { useContextSWR } from './use-context-swr';
 import { useContextSWR } from './use-context-swr';
@@ -74,8 +75,8 @@ export const useAcceptedUploadFileType = (): SWRResponse<
   AcceptedUploadFileType,
   AcceptedUploadFileType,
   Error
   Error
 > => {
 > => {
-  const [isUploadEnabled] = useIsUploadEnabled();
-  const [isUploadAllFileAllowed] = useIsUploadAllFileAllowed();
+  const isUploadEnabled = useAtomValue(isUploadEnabledAtom);
+  const isUploadAllFileAllowed = useAtomValue(isUploadAllFileAllowedAtom);
 
 
   return useSWRImmutable(
   return useSWRImmutable(
     ['acceptedUploadFileType', isUploadEnabled, isUploadAllFileAllowed],
     ['acceptedUploadFileType', isUploadEnabled, isUploadAllFileAllowed],

+ 2 - 2
apps/app/src/stores-universal/use-next-themes.tsx

@@ -14,7 +14,7 @@ export type Themes = (typeof Themes)[keyof typeof Themes];
 const ATTRIBUTE = 'data-bs-theme';
 const ATTRIBUTE = 'data-bs-theme';
 
 
 export const NextThemesProvider: React.FC<ThemeProviderProps> = (props) => {
 export const NextThemesProvider: React.FC<ThemeProviderProps> = (props) => {
-  const [forcedColorScheme] = useForcedColorScheme();
+  const forcedColorScheme = useForcedColorScheme();
 
 
   return (
   return (
     <ThemeProvider
     <ThemeProvider
@@ -36,7 +36,7 @@ type UseThemeExtendedProps = Omit<UseThemeProps, 'theme' | 'resolvedTheme'> & {
 
 
 export const useNextThemes = (): UseThemeProps & UseThemeExtendedProps => {
 export const useNextThemes = (): UseThemeProps & UseThemeExtendedProps => {
   const props = useTheme();
   const props = useTheme();
-  const [forcedColorScheme] = useForcedColorScheme();
+  const forcedColorScheme = useForcedColorScheme();
 
 
   const resolvedTheme =
   const resolvedTheme =
     forcedColorScheme ?? (props.resolvedTheme as ColorScheme);
     forcedColorScheme ?? (props.resolvedTheme as ColorScheme);

+ 6 - 5
apps/app/src/stores/editor.tsx

@@ -3,6 +3,7 @@ import { useCallback, useEffect } from 'react';
 import { type Nullable } from '@growi/core';
 import { type Nullable } from '@growi/core';
 import { withUtils, type SWRResponseWithUtils, useSWRStatic } from '@growi/core/dist/swr';
 import { withUtils, type SWRResponseWithUtils, useSWRStatic } from '@growi/core/dist/swr';
 import type { EditorSettings } from '@growi/editor';
 import type { EditorSettings } from '@growi/editor';
+import { useAtomValue } from 'jotai';
 import useSWR, { type SWRResponse } from 'swr';
 import useSWR, { type SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 import useSWRImmutable from 'swr/immutable';
 
 
@@ -11,7 +12,7 @@ import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
 import type { SlackChannels } from '~/interfaces/user-trigger-notification';
 import type { SlackChannels } from '~/interfaces/user-trigger-notification';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
 import { useCurrentUser } from '~/states/global';
 import { useCurrentUser } from '~/states/global';
-import { useDefaultIndentSize } from '~/states/server-configurations';
+import { defaultIndentSizeAtom } from '~/states/server-configurations';
 
 
 import { useSWRxTagsInfo } from './page';
 import { useSWRxTagsInfo } from './page';
 
 
@@ -29,9 +30,9 @@ type EditorSettingsOperation = {
 //   - Unabling localStorageMiddleware occurrs a flickering problem when loading theme.
 //   - Unabling localStorageMiddleware occurrs a flickering problem when loading theme.
 //   - see: https://github.com/weseek/growi/pull/6781#discussion_r1000285786
 //   - see: https://github.com/weseek/growi/pull/6781#discussion_r1000285786
 export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperation, EditorSettings, Error> => {
 export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperation, EditorSettings, Error> => {
-  const [currentUser] = useCurrentUser();
-  const [isGuestUser] = useIsGuestUser();
-  const [isReadOnlyUser] = useIsReadOnlyUser();
+  const currentUser = useCurrentUser();
+  const isGuestUser = useIsGuestUser();
+  const isReadOnlyUser = useIsReadOnlyUser();
 
 
   const swrResult = useSWRImmutable(
   const swrResult = useSWRImmutable(
     (isGuestUser || isReadOnlyUser) ? null : ['/personal-setting/editor-settings', currentUser?.username],
     (isGuestUser || isReadOnlyUser) ? null : ['/personal-setting/editor-settings', currentUser?.username],
@@ -61,7 +62,7 @@ export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperatio
 };
 };
 
 
 export const useCurrentIndentSize = (): SWRResponse<number, Error> => {
 export const useCurrentIndentSize = (): SWRResponse<number, Error> => {
-  const [defaultIndentSize] = useDefaultIndentSize();
+  const defaultIndentSize = useAtomValue(defaultIndentSizeAtom);
   return useSWRStatic<number, Error>(
   return useSWRStatic<number, Error>(
     defaultIndentSize == null ? null : 'currentIndentSize',
     defaultIndentSize == null ? null : 'currentIndentSize',
     undefined,
     undefined,

+ 6 - 6
apps/app/src/stores/page.tsx

@@ -53,7 +53,7 @@ export const useSWRxPageByPath = (path?: string, config?: SWRConfiguration): SWR
 };
 };
 
 
 export const useSWRxTagsInfo = (pageId: Nullable<string>, config?: SWRConfiguration): SWRResponse<IPageTagsInfo | null, Error> => {
 export const useSWRxTagsInfo = (pageId: Nullable<string>, config?: SWRConfiguration): SWRResponse<IPageTagsInfo | null, Error> => {
-  const [shareLinkId] = useShareLinkId();
+  const shareLinkId = useShareLinkId();
 
 
   const endpoint = `/pages.getPageTag?pageId=${pageId}`;
   const endpoint = `/pages.getPageTag?pageId=${pageId}`;
 
 
@@ -83,7 +83,7 @@ export const useSWRxPageInfo = (
 ): SWRResponse<IPageInfo | IPageInfoForOperation> => {
 ): SWRResponse<IPageInfo | IPageInfoForOperation> => {
 
 
   // Cache remains from guest mode when logging in via the Login lead, so add 'isGuestUser' key
   // Cache remains from guest mode when logging in via the Login lead, so add 'isGuestUser' key
-  const [isGuestUser] = useIsGuestUser();
+  const isGuestUser = useIsGuestUser();
 
 
   // assign null if shareLinkId is undefined in order to identify SWR key only by pageId
   // assign null if shareLinkId is undefined in order to identify SWR key only by pageId
   const fixedShareLinkId = shareLinkId ?? null;
   const fixedShareLinkId = shareLinkId ?? null;
@@ -117,7 +117,7 @@ export const useSWRMUTxPageInfo = (
 ): SWRMutationResponse<IPageInfo | IPageInfoForOperation> => {
 ): SWRMutationResponse<IPageInfo | IPageInfoForOperation> => {
 
 
   // Cache remains from guest mode when logging in via the Login lead, so add 'isGuestUser' key
   // Cache remains from guest mode when logging in via the Login lead, so add 'isGuestUser' key
-  const [isGuestUser] = useIsGuestUser();
+  const isGuestUser = useIsGuestUser();
 
 
   // assign null if shareLinkId is undefined in order to identify SWR key only by pageId
   // assign null if shareLinkId is undefined in order to identify SWR key only by pageId
   const fixedShareLinkId = shareLinkId ?? null;
   const fixedShareLinkId = shareLinkId ?? null;
@@ -173,9 +173,9 @@ export const useSWRxCurrentGrantData = (
     pageId: string | null | undefined,
     pageId: string | null | undefined,
 ): SWRResponse<IResCurrentGrantData, Error> => {
 ): SWRResponse<IResCurrentGrantData, Error> => {
 
 
-  const [isGuestUser] = useIsGuestUser();
-  const [isReadOnlyUser] = useIsReadOnlyUser();
-  const [isNotFound] = usePageNotFound();
+  const isGuestUser = useIsGuestUser();
+  const isReadOnlyUser = useIsReadOnlyUser();
+  const isNotFound = usePageNotFound();
 
 
   const key = !isGuestUser && !isReadOnlyUser && !isNotFound && pageId != null
   const key = !isGuestUser && !isReadOnlyUser && !isNotFound && pageId != null
     ? ['/page/grant-data', pageId]
     ? ['/page/grant-data', pageId]

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

@@ -16,7 +16,7 @@ import { useCurrentPageTocNode } from './ui';
 const logger = loggerFactory('growi:cli:services:renderer');
 const logger = loggerFactory('growi:cli:services:renderer');
 
 
 const useRendererConfigExt = (): RendererConfigExt | null => {
 const useRendererConfigExt = (): RendererConfigExt | null => {
-  const [rendererConfig] = useRendererConfig();
+  const rendererConfig = useRendererConfig();
   const { isDarkMode } = useNextThemes();
   const { isDarkMode } = useNextThemes();
 
 
   return rendererConfig == null ? null : {
   return rendererConfig == null ? null : {
@@ -27,7 +27,7 @@ const useRendererConfigExt = (): RendererConfigExt | null => {
 
 
 
 
 export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
 export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
-  const [currentPagePath] = useCurrentPagePath();
+  const currentPagePath = useCurrentPagePath();
   const rendererConfig = useRendererConfigExt();
   const rendererConfig = useRendererConfigExt();
   const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
   const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
 
 
@@ -59,7 +59,7 @@ export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
 };
 };
 
 
 export const useTocOptions = (): SWRResponse<RendererOptions, Error> => {
 export const useTocOptions = (): SWRResponse<RendererOptions, Error> => {
-  const [currentPagePath] = useCurrentPagePath();
+  const currentPagePath = useCurrentPagePath();
   const rendererConfig = useRendererConfigExt();
   const rendererConfig = useRendererConfigExt();
   const { data: tocNode } = useCurrentPageTocNode();
   const { data: tocNode } = useCurrentPageTocNode();
 
 
@@ -82,7 +82,7 @@ export const useTocOptions = (): SWRResponse<RendererOptions, Error> => {
 };
 };
 
 
 export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
 export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
-  const [currentPagePath] = useCurrentPagePath();
+  const currentPagePath = useCurrentPagePath();
   const rendererConfig = useRendererConfigExt();
   const rendererConfig = useRendererConfigExt();
 
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
@@ -109,7 +109,7 @@ export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
 };
 };
 
 
 export const useCommentForCurrentPageOptions = (): SWRResponse<RendererOptions, Error> => {
 export const useCommentForCurrentPageOptions = (): SWRResponse<RendererOptions, Error> => {
-  const [currentPagePath] = useCurrentPagePath();
+  const currentPagePath = useCurrentPagePath();
   const rendererConfig = useRendererConfigExt();
   const rendererConfig = useRendererConfigExt();
 
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
@@ -182,7 +182,7 @@ export const useCustomSidebarOptions = (config?: SWRConfiguration): SWRResponse<
 };
 };
 
 
 export const usePresentationViewOptions = (): SWRResponse<RendererOptions, Error> => {
 export const usePresentationViewOptions = (): SWRResponse<RendererOptions, Error> => {
-  const [currentPagePath] = useCurrentPagePath();
+  const currentPagePath = useCurrentPagePath();
   const rendererConfig = useRendererConfigExt();
   const rendererConfig = useRendererConfigExt();
 
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
   const isAllDataValid = currentPagePath != null && rendererConfig != null;

+ 21 - 21
apps/app/src/stores/ui.tsx

@@ -40,7 +40,7 @@ const logger = loggerFactory('growi:stores:ui');
  *********************************************************** */
  *********************************************************** */
 
 
 export const useCurrentPageTocNode = (): SWRResponse<HtmlElementNode, any> => {
 export const useCurrentPageTocNode = (): SWRResponse<HtmlElementNode, any> => {
-  const [currentPagePath] = useCurrentPagePath();
+  const currentPagePath = useCurrentPagePath();
 
 
   return useStaticSWR(['currentPageTocNode', currentPagePath]);
   return useStaticSWR(['currentPageTocNode', currentPagePath]);
 };
 };
@@ -233,13 +233,13 @@ export const useCommentEditorDirtyMap = (): SWRResponse<Map<string, boolean>, Er
 export const useIsAbleToShowTrashPageManagementButtons = (): SWRResponse<boolean, Error> => {
 export const useIsAbleToShowTrashPageManagementButtons = (): SWRResponse<boolean, Error> => {
   const key = 'isAbleToShowTrashPageManagementButtons';
   const key = 'isAbleToShowTrashPageManagementButtons';
 
 
-  const [_currentUser] = useCurrentUser();
+  const _currentUser = useCurrentUser();
   const isCurrentUserExist = _currentUser != null;
   const isCurrentUserExist = _currentUser != null;
 
 
-  const [_currentPageId] = useCurrentPageId();
-  const [_isNotFound] = usePageNotFound();
-  const [_isTrashPage] = useIsTrashPage();
-  const [_isReadOnlyUser] = useIsReadOnlyUser();
+  const _currentPageId = useCurrentPageId();
+  const _isNotFound = usePageNotFound();
+  const _isTrashPage = useIsTrashPage();
+  const _isReadOnlyUser = useIsReadOnlyUser();
   const isPageExist = _currentPageId != null && _isNotFound === false;
   const isPageExist = _currentPageId != null && _isNotFound === false;
   const isTrashPage = isPageExist && _isTrashPage === true;
   const isTrashPage = isPageExist && _isTrashPage === true;
   const isReadOnlyUser = isPageExist && _isReadOnlyUser === true;
   const isReadOnlyUser = isPageExist && _isReadOnlyUser === true;
@@ -254,10 +254,10 @@ export const useIsAbleToShowTrashPageManagementButtons = (): SWRResponse<boolean
 
 
 export const useIsAbleToShowPageManagement = (): SWRResponse<boolean, Error> => {
 export const useIsAbleToShowPageManagement = (): SWRResponse<boolean, Error> => {
   const key = 'isAbleToShowPageManagement';
   const key = 'isAbleToShowPageManagement';
-  const [currentPageId] = useCurrentPageId();
-  const [isNotFound] = usePageNotFound();
-  const [_isTrashPage] = useIsTrashPage();
-  const [_isSharedUser] = useIsSharedUser();
+  const currentPageId = useCurrentPageId();
+  const isNotFound = usePageNotFound();
+  const _isTrashPage = useIsTrashPage();
+  const _isSharedUser = useIsSharedUser();
 
 
   const pageId = currentPageId;
   const pageId = currentPageId;
   const includesUndefined = [pageId, _isTrashPage, _isSharedUser, isNotFound].some(v => v === undefined);
   const includesUndefined = [pageId, _isTrashPage, _isSharedUser, isNotFound].some(v => v === undefined);
@@ -274,12 +274,12 @@ export const useIsAbleToShowPageManagement = (): SWRResponse<boolean, Error> =>
 
 
 export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
 export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
   const key = 'isAbleToShowTagLabel';
   const key = 'isAbleToShowTagLabel';
-  const [pageId] = useCurrentPageId();
-  const [isNotFound] = usePageNotFound();
-  const [currentPagePath] = useCurrentPagePath();
-  const [isIdenticalPath] = useIsIdenticalPath();
+  const pageId = useCurrentPageId();
+  const isNotFound = usePageNotFound();
+  const currentPagePath = useCurrentPagePath();
+  const isIdenticalPath = useIsIdenticalPath();
   const { editorMode } = useEditorMode();
   const { editorMode } = useEditorMode();
-  const [shareLinkId] = useShareLinkId();
+  const shareLinkId = useShareLinkId();
 
 
 
 
   const includesUndefined = [currentPagePath, isIdenticalPath, isNotFound, editorMode].some(v => v === undefined);
   const includesUndefined = [currentPagePath, isIdenticalPath, isNotFound, editorMode].some(v => v === undefined);
@@ -296,8 +296,8 @@ export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
 
 
 export const useIsAbleToChangeEditorMode = (): SWRResponse<boolean, Error> => {
 export const useIsAbleToChangeEditorMode = (): SWRResponse<boolean, Error> => {
   const key = 'isAbleToChangeEditorMode';
   const key = 'isAbleToChangeEditorMode';
-  const [isEditable] = useIsEditable();
-  const [isSharedUser] = useIsSharedUser();
+  const isEditable = useIsEditable();
+  const isSharedUser = useIsSharedUser();
 
 
   const includesUndefined = [isEditable, isSharedUser].some(v => v === undefined);
   const includesUndefined = [isEditable, isSharedUser].some(v => v === undefined);
 
 
@@ -309,9 +309,9 @@ export const useIsAbleToChangeEditorMode = (): SWRResponse<boolean, Error> => {
 
 
 export const useIsAbleToShowPageAuthors = (): SWRResponse<boolean, Error> => {
 export const useIsAbleToShowPageAuthors = (): SWRResponse<boolean, Error> => {
   const key = 'isAbleToShowPageAuthors';
   const key = 'isAbleToShowPageAuthors';
-  const [pageId] = useCurrentPageId();
-  const [isNotFound] = usePageNotFound();
-  const [pagePath] = useCurrentPagePath();
+  const pageId = useCurrentPageId();
+  const isNotFound = usePageNotFound();
+  const pagePath = useCurrentPagePath();
 
 
   const includesUndefined = [pageId, pagePath, isNotFound].some(v => v === undefined);
   const includesUndefined = [pageId, pagePath, isNotFound].some(v => v === undefined);
   const isPageExist = (pageId != null) && !isNotFound;
   const isPageExist = (pageId != null) && !isNotFound;
@@ -326,7 +326,7 @@ export const useIsAbleToShowPageAuthors = (): SWRResponse<boolean, Error> => {
 export const useIsUntitledPage = (): SWRResponse<boolean> => {
 export const useIsUntitledPage = (): SWRResponse<boolean> => {
   const key = 'isUntitledPage';
   const key = 'isUntitledPage';
 
 
-  const [pageId] = useCurrentPageId();
+  const pageId = useCurrentPageId();
 
 
   return useSWRStatic(
   return useSWRStatic(
     pageId == null ? null : [key, pageId],
     pageId == null ? null : [key, pageId],