Browse Source

optimize PageCreateModal and PageRenameModal

Yuki Takei 6 months ago
parent
commit
e2a4aa3014

+ 30 - 28
apps/app/src/client/components/PageCreateModal.tsx

@@ -43,11 +43,16 @@ const PageCreateModal: React.FC = () => {
   const { createTemplate } = useCreateTemplatePage();
   const { createTemplate } = useCreateTemplatePage();
 
 
   const isReachable = useAtomValue(isSearchServiceReachableAtom);
   const isReachable = useAtomValue(isSearchServiceReachableAtom);
-  const userHomepagePath = pagePathUtils.userHomepagePath(currentUser);
-  const isCreatable = isCreatablePage(pathname) || isUsersHomepage(pathname);
-  const pageNameInputInitialValue = isCreatable ? pathUtils.addTrailingSlash(pathname) : '/';
-  const now = format(new Date(), 'yyyy/MM/dd');
-  const todaysParentPath = [userHomepagePath, t('create_page_dropdown.todays.memo', { ns: 'commons' }), now].join('/');
+
+  // Memoize computed values
+  const userHomepagePath = useMemo(() => pagePathUtils.userHomepagePath(currentUser), [currentUser]);
+  const isCreatable = useMemo(() => isCreatablePage(pathname) || isUsersHomepage(pathname), [pathname]);
+  const pageNameInputInitialValue = useMemo(() => (isCreatable ? pathUtils.addTrailingSlash(pathname) : '/'), [isCreatable, pathname]);
+  const now = useMemo(() => format(new Date(), 'yyyy/MM/dd'), []);
+  const todaysParentPath = useMemo(
+    () => [userHomepagePath, t('create_page_dropdown.todays.memo', { ns: 'commons' }), now].join('/'),
+    [userHomepagePath, t, now],
+  );
 
 
   const [todayInput, setTodayInput] = useState('');
   const [todayInput, setTodayInput] = useState('');
   const [pageNameInput, setPageNameInput] = useState(pageNameInputInitialValue);
   const [pageNameInput, setPageNameInput] = useState(pageNameInputInitialValue);
@@ -55,41 +60,38 @@ const PageCreateModal: React.FC = () => {
   const [isMatchedWithUserHomepagePath, setIsMatchedWithUserHomepagePath] = useState(false);
   const [isMatchedWithUserHomepagePath, setIsMatchedWithUserHomepagePath] = useState(false);
 
 
   const checkIsUsersHomepageDebounce = useMemo(() => {
   const checkIsUsersHomepageDebounce = useMemo(() => {
-    const checkIsUsersHomepage = () => {
-      setIsMatchedWithUserHomepagePath(isUsersHomepage(pageNameInput));
-    };
-
-    return debounce(1000, checkIsUsersHomepage);
-  }, [pageNameInput]);
+    return debounce(1000, (input: string) => {
+      setIsMatchedWithUserHomepagePath(isUsersHomepage(input));
+    });
+  }, []);
 
 
   useEffect(() => {
   useEffect(() => {
     if (isOpened) {
     if (isOpened) {
-      checkIsUsersHomepageDebounce();
+      checkIsUsersHomepageDebounce(pageNameInput);
     }
     }
   }, [isOpened, checkIsUsersHomepageDebounce, pageNameInput]);
   }, [isOpened, checkIsUsersHomepageDebounce, pageNameInput]);
 
 
-
-  function transitBySubmitEvent(e, transitHandler) {
+  const transitBySubmitEvent = useCallback((e, transitHandler) => {
     // prevent page transition by submit
     // prevent page transition by submit
     e.preventDefault();
     e.preventDefault();
     transitHandler();
     transitHandler();
-  }
+  }, []);
 
 
   /**
   /**
    * change todayInput
    * change todayInput
    * @param {string} value
    * @param {string} value
    */
    */
-  function onChangeTodayInputHandler(value) {
+  const onChangeTodayInputHandler = useCallback((value) => {
     setTodayInput(value);
     setTodayInput(value);
-  }
+  }, []);
 
 
   /**
   /**
    * change template
    * change template
    * @param {string} value
    * @param {string} value
    */
    */
-  function onChangeTemplateHandler(value) {
+  const onChangeTemplateHandler = useCallback((value) => {
     setTemplate(value);
     setTemplate(value);
-  }
+  }, []);
 
 
   /**
   /**
    * access today page
    * access today page
@@ -137,7 +139,7 @@ const PageCreateModal: React.FC = () => {
   const createInputPageWithToastr = useToastrOnError(createInputPage);
   const createInputPageWithToastr = useToastrOnError(createInputPage);
   const createTemplateWithToastr = useToastrOnError(createTemplatePage);
   const createTemplateWithToastr = useToastrOnError(createTemplatePage);
 
 
-  function renderCreateTodayForm() {
+  const renderCreateTodayForm = useMemo(() => {
     if (!isOpened) {
     if (!isOpened) {
       return <></>;
       return <></>;
     }
     }
@@ -180,9 +182,9 @@ const PageCreateModal: React.FC = () => {
         </fieldset>
         </fieldset>
       </div>
       </div>
     );
     );
-  }
+  }, [isOpened, todaysParentPath, todayInput, t, onChangeTodayInputHandler, transitBySubmitEvent, createTodaysMemoWithToastr]);
 
 
-  function renderInputPageForm() {
+  const renderInputPageForm = useMemo(() => {
     if (!isOpened) {
     if (!isOpened) {
       return <></>;
       return <></>;
     }
     }
@@ -237,9 +239,9 @@ const PageCreateModal: React.FC = () => {
         </fieldset>
         </fieldset>
       </div>
       </div>
     );
     );
-  }
+  }, [isOpened, isReachable, pageNameInputInitialValue, createInputPageWithToastr, pageNameInput, isMatchedWithUserHomepagePath, t, transitBySubmitEvent]);
 
 
-  function renderTemplatePageForm() {
+  const renderTemplatePageForm = useMemo(() => {
     if (!isOpened) {
     if (!isOpened) {
       return <></>;
       return <></>;
     }
     }
@@ -289,7 +291,7 @@ const PageCreateModal: React.FC = () => {
         </fieldset>
         </fieldset>
       </div>
       </div>
     );
     );
-  }
+  }, [isOpened, pathname, template, onChangeTemplateHandler, createTemplateWithToastr, t]);
 
 
   return (
   return (
     <Modal
     <Modal
@@ -304,9 +306,9 @@ const PageCreateModal: React.FC = () => {
         {t('New Page')}
         {t('New Page')}
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>
-        {renderCreateTodayForm()}
-        {renderInputPageForm()}
-        {renderTemplatePageForm()}
+        {renderCreateTodayForm}
+        {renderInputPageForm}
+        {renderTemplatePageForm}
       </ModalBody>
       </ModalBody>
     </Modal>
     </Modal>
 
 

+ 41 - 16
apps/app/src/client/components/PageRenameModal.tsx

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useState, useEffect, useCallback, useMemo, type JSX,
+  useState, useEffect, useCallback, useMemo,
 } from 'react';
 } from 'react';
 
 
 import { isIPageInfoForEntity } from '@growi/core';
 import { isIPageInfoForEntity } from '@growi/core';
@@ -26,8 +26,10 @@ const isV5Compatible = (meta: unknown): boolean => {
   return isIPageInfoForEntity(meta) ? meta.isV5Compatible : true;
   return isIPageInfoForEntity(meta) ? meta.isV5Compatible : true;
 };
 };
 
 
-
-const PageRenameModal = (): JSX.Element => {
+/**
+ * PageRenameModalSubstance - Heavy processing component (rendered only when modal is open)
+ */
+const PageRenameModalSubstance: React.FC = () => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   const { isUsersHomepage } = pagePathUtils;
   const { isUsersHomepage } = pagePathUtils;
@@ -79,15 +81,19 @@ const PageRenameModal = (): JSX.Element => {
     }
     }
   }, [isOpened, page, updateSubordinatedList]);
   }, [isOpened, page, updateSubordinatedList]);
 
 
+  // Memoize computed values
+  const isTargetPageDuplicate = useMemo(() => existingPaths.includes(pageNameInput), [existingPaths, pageNameInput]);
+  const isV5CompatiblePage = useMemo(() => (page != null ? isV5Compatible(page.meta) : true), [page]);
+
   const canRename = useMemo(() => {
   const canRename = useMemo(() => {
     if (page == null || isMatchedWithUserHomepagePath || page.data.path === pageNameInput) {
     if (page == null || isMatchedWithUserHomepagePath || page.data.path === pageNameInput) {
       return false;
       return false;
     }
     }
-    if (isV5Compatible(page.meta)) {
+    if (isV5CompatiblePage) {
       return existingPaths.length === 0; // v5 data
       return existingPaths.length === 0; // v5 data
     }
     }
     return isRenameRecursively; // v4 data
     return isRenameRecursively; // v4 data
-  }, [existingPaths.length, isMatchedWithUserHomepagePath, isRenameRecursively, page, pageNameInput]);
+  }, [existingPaths.length, isMatchedWithUserHomepagePath, isRenameRecursively, page, pageNameInput, isV5CompatiblePage]);
 
 
   const rename = useCallback(async() => {
   const rename = useCallback(async() => {
     if (page == null || !canRename) {
     if (page == null || !canRename) {
@@ -175,10 +181,10 @@ const PageRenameModal = (): JSX.Element => {
    * change pageNameInput
    * change pageNameInput
    * @param {string} value
    * @param {string} value
    */
    */
-  function inputChangeHandler(value) {
+  const inputChangeHandler = useCallback((value) => {
     setErrs(null);
     setErrs(null);
     setPageNameInput(value);
     setPageNameInput(value);
-  }
+  }, []);
 
 
   useEffect(() => {
   useEffect(() => {
     if (isOpened || page == null) {
     if (isOpened || page == null) {
@@ -199,13 +205,12 @@ const PageRenameModal = (): JSX.Element => {
 
 
   }, [isOpened, page]);
   }, [isOpened, page]);
 
 
-  const bodyContent = () => {
+  const bodyContent = useMemo(() => {
     if (!isOpened || page == null) {
     if (!isOpened || page == null) {
       return <></>;
       return <></>;
     }
     }
 
 
     const { path } = page.data;
     const { path } = page.data;
-    const isTargetPageDuplicate = existingPaths.includes(pageNameInput);
 
 
     return (
     return (
       <>
       <>
@@ -324,9 +329,12 @@ const PageRenameModal = (): JSX.Element => {
         </Collapse>
         </Collapse>
       </>
       </>
     );
     );
-  };
+  }, [isOpened, page, siteUrl, t, pageNameInput, isTargetPageDuplicate, isMatchedWithUserHomepagePath,
+      isReachable, ppacInputChangeHandler, rename, inputChangeHandler,
+      isRenameRecursively, existingPaths, expandOtherOptions, isRenameRedirect,
+      isRemainMetadata, subordinatedError]);
 
 
-  const footerContent = () => {
+  const footerContent = useMemo(() => {
     if (!isOpened || page == null) {
     if (!isOpened || page == null) {
       return <></>;
       return <></>;
     }
     }
@@ -346,20 +354,37 @@ const PageRenameModal = (): JSX.Element => {
         </button>
         </button>
       </>
       </>
     );
     );
-  };
-
+  }, [isOpened, page, canRename, errs, pageNameInput, rename]);
 
 
   return (
   return (
-    <Modal size="lg" isOpen={isOpened} toggle={closeRenameModal} data-testid="page-rename-modal" autoFocus={false}>
+    <>
       <ModalHeader tag="h4" toggle={closeRenameModal}>
       <ModalHeader tag="h4" toggle={closeRenameModal}>
         { t('modal_rename.label.Move/Rename page') }
         { t('modal_rename.label.Move/Rename page') }
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>
-        {bodyContent()}
+        {bodyContent}
       </ModalBody>
       </ModalBody>
       <ModalFooter>
       <ModalFooter>
-        {footerContent()}
+        {footerContent}
       </ModalFooter>
       </ModalFooter>
+    </>
+  );
+};
+
+/**
+ * PageRenameModal - Container component (lightweight, always rendered)
+ */
+const PageRenameModal = (): React.JSX.Element => {
+  const { isOpened } = usePageRenameModalStatus();
+  const { close: closeRenameModal } = usePageRenameModalActions();
+
+  if (!isOpened) {
+    return <></>;
+  }
+
+  return (
+    <Modal size="lg" isOpen={isOpened} toggle={closeRenameModal} data-testid="page-rename-modal" autoFocus={false}>
+      <PageRenameModalSubstance />
     </Modal>
     </Modal>
   );
   );
 };
 };