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

refactor some modal to fix early return problem

Yuki Takei 5 месяцев назад
Родитель
Сommit
554136b813

+ 50 - 51
apps/app/src/client/components/Common/ImageCropModal.tsx

@@ -134,60 +134,59 @@ const ImageCropModal: FC<Props> = (props: Props) => {
   const toggleCropMode = useCallback(() => setIsCropImage(!isCropImage), [isCropImage]);
   const handleCropChange = useCallback((crop: CropOptions) => setCropOtions(crop), []);
 
-  // Early return optimization
-  if (!isShow) {
-    return <></>;
-  }
-
   return (
     <Modal isOpen={isShow} toggle={onModalCloseHandler}>
-      <ModalHeader tag="h4" toggle={onModalCloseHandler} className="text-info">
-        {t('crop_image_modal.image_crop')}
-      </ModalHeader>
-      <ModalBody className="my-4">
-        {
-          isCropImage
-            ? (
-              <ReactCrop
-                style={{ backgroundColor: 'transparent' }}
-                src={src}
-                crop={cropOptions}
-                onImageLoaded={onImageLoaded}
-                onChange={handleCropChange}
-                circularCrop={isCircular}
-              />
+      {isShow && (
+        <>
+          <ModalHeader tag="h4" toggle={onModalCloseHandler} className="text-info">
+            {t('crop_image_modal.image_crop')}
+          </ModalHeader>
+          <ModalBody className="my-4">
+            {
+              isCropImage
+                ? (
+                  <ReactCrop
+                    style={{ backgroundColor: 'transparent' }}
+                    src={src}
+                    crop={cropOptions}
+                    onImageLoaded={onImageLoaded}
+                    onChange={handleCropChange}
+                    circularCrop={isCircular}
+                  />
+                )
+                : (<img style={{ maxWidth: imageRef?.width }} src={imageRef?.src} />)
+            }
+          </ModalBody>
+          <ModalFooter>
+            <button type="button" className="btn btn-outline-danger rounded-pill me-auto" disabled={!isCropImage} onClick={reset}>
+              {t('commons:Reset')}
+            </button>
+            { !showCropOption && (
+              <div className="me-auto">
+                <div className="form-check form-switch">
+                  <input
+                    id="cropImageOption"
+                    className="form-check-input me-auto"
+                    type="checkbox"
+                    checked={isCropImage}
+                    onChange={toggleCropMode}
+                  />
+                  <label className="form-label form-check-label" htmlFor="cropImageOption">
+                    { t('crop_image_modal.image_crop') }
+                  </label>
+                </div>
+              </div>
             )
-            : (<img style={{ maxWidth: imageRef?.width }} src={imageRef?.src} />)
-        }
-      </ModalBody>
-      <ModalFooter>
-        <button type="button" className="btn btn-outline-danger rounded-pill me-auto" disabled={!isCropImage} onClick={reset}>
-          {t('commons:Reset')}
-        </button>
-        { !showCropOption && (
-          <div className="me-auto">
-            <div className="form-check form-switch">
-              <input
-                id="cropImageOption"
-                className="form-check-input me-auto"
-                type="checkbox"
-                checked={isCropImage}
-                onChange={toggleCropMode}
-              />
-              <label className="form-label form-check-label" htmlFor="cropImageOption">
-                { t('crop_image_modal.image_crop') }
-              </label>
-            </div>
-          </div>
-        )
-        }
-        <button type="button" className="btn btn-outline-secondary rounded-pill me-2" onClick={onModalCloseHandler}>
-          {t('crop_image_modal.cancel')}
-        </button>
-        <button type="button" className="btn btn-outline-primary rounded-pill" onClick={processAndSaveImage}>
-          { isCropImage ? t('crop_image_modal.crop') : t('crop_image_modal.save') }
-        </button>
-      </ModalFooter>
+            }
+            <button type="button" className="btn btn-outline-secondary rounded-pill me-2" onClick={onModalCloseHandler}>
+              {t('crop_image_modal.cancel')}
+            </button>
+            <button type="button" className="btn btn-outline-primary rounded-pill" onClick={processAndSaveImage}>
+              { isCropImage ? t('crop_image_modal.crop') : t('crop_image_modal.save') }
+            </button>
+          </ModalFooter>
+        </>
+      )}
     </Modal>
   );
 };

+ 19 - 19
apps/app/src/client/components/CreateTemplateModal.tsx

@@ -82,27 +82,27 @@ export const CreateTemplateModal: React.FC<CreateTemplateModalProps> = ({
     </div>
   ), [isCreating, onClickTemplateButtonHandler]);
 
-  if (!isCreatable) {
-    return <></>;
-  }
-
   return (
     <Modal isOpen={isOpen} toggle={onClose} data-testid="page-template-modal">
-      <ModalHeader tag="h4" toggle={onClose}>
-        {t('template.modal_label.Create/Edit Template Page')}
-      </ModalHeader>
-      <ModalBody>
-        <div>
-          <label className="form-label mb-4">
-            <code>{parentPath}</code><br />
-            {t('template.modal_label.Create template under')}
-          </label>
-          <div className="row row-cols-2">
-            {renderTemplateCard('children', '_template')}
-            {renderTemplateCard('descendants', '__template')}
-          </div>
-        </div>
-      </ModalBody>
+      {(isCreatable && isOpen) && (
+        <>
+          <ModalHeader tag="h4" toggle={onClose}>
+            {t('template.modal_label.Create/Edit Template Page')}
+          </ModalHeader>
+          <ModalBody>
+            <div>
+              <label className="form-label mb-4">
+                <code>{parentPath}</code><br />
+                {t('template.modal_label.Create template under')}
+              </label>
+              <div className="row row-cols-2">
+                {renderTemplateCard('children', '_template')}
+                {renderTemplateCard('descendants', '__template')}
+              </div>
+            </div>
+          </ModalBody>
+        </>
+      )}
     </Modal>
   );
 };

+ 35 - 8
apps/app/src/client/components/PageComment/DeleteCommentModal.tsx

@@ -22,9 +22,19 @@ export type DeleteCommentModalProps = {
   confirmToDelete: () => void,
 }
 
-export const DeleteCommentModal = (props: DeleteCommentModalProps): React.JSX.Element => {
+/**
+ * DeleteCommentModalSubstance - Presentation component (heavy logic, rendered only when isOpen)
+ */
+type DeleteCommentModalSubstanceProps = {
+  comment: ICommentHasId,
+  errorMessage: string,
+  cancelToDelete: () => void,
+  confirmToDelete: () => void,
+}
+
+const DeleteCommentModalSubstance = (props: DeleteCommentModalSubstanceProps): React.JSX.Element => {
   const {
-    isShown, comment, errorMessage, cancelToDelete, confirmToDelete,
+    comment, errorMessage, cancelToDelete, confirmToDelete,
   } = props;
 
   const { t } = useTranslation();
@@ -82,13 +92,8 @@ export const DeleteCommentModal = (props: DeleteCommentModalProps): React.JSX.El
     </>
   ), [errorMessage, cancelToDelete, confirmToDelete, t]);
 
-  // Early return after all hooks
-  if (!isShown || comment == null) {
-    return <></>;
-  }
-
   return (
-    <Modal data-testid="page-comment-delete-modal" isOpen={isShown} toggle={cancelToDelete} className={`${styles['page-comment-delete-modal']}`}>
+    <>
       <ModalHeader tag="h4" toggle={cancelToDelete} className="text-danger">
         {headerContent}
       </ModalHeader>
@@ -98,6 +103,28 @@ export const DeleteCommentModal = (props: DeleteCommentModalProps): React.JSX.El
       <ModalFooter>
         {footerContent}
       </ModalFooter>
+    </>
+  );
+};
+
+/**
+ * DeleteCommentModal - Container component (lightweight, always rendered)
+ */
+export const DeleteCommentModal = (props: DeleteCommentModalProps): React.JSX.Element => {
+  const {
+    isShown, comment, errorMessage, cancelToDelete, confirmToDelete,
+  } = props;
+
+  return (
+    <Modal data-testid="page-comment-delete-modal" isOpen={isShown} toggle={cancelToDelete} className={`${styles['page-comment-delete-modal']}`}>
+      {isShown && comment != null && (
+        <DeleteCommentModalSubstance
+          comment={comment}
+          errorMessage={errorMessage}
+          cancelToDelete={cancelToDelete}
+          confirmToDelete={confirmToDelete}
+        />
+      )}
     </Modal>
   );
 };

+ 19 - 9
apps/app/src/client/components/ShortcutsModal.tsx

@@ -8,10 +8,11 @@ import { useShortcutsModalStatus, useShortcutsModalActions } from '~/states/ui/m
 import styles from './ShortcutsModal.module.scss';
 
 
-const ShortcutsModal = (): React.JSX.Element => {
+/**
+ * ShortcutsModalSubstance - Presentation component (heavy logic, rendered only when isOpen)
+ */
+const ShortcutsModalSubstance = (): React.JSX.Element => {
   const { t } = useTranslation();
-
-  const status = useShortcutsModalStatus();
   const { close } = useShortcutsModalActions();
 
   // Memoize OS-specific class
@@ -400,19 +401,28 @@ const ShortcutsModal = (): React.JSX.Element => {
     );
   }, [additionalClassByOs, t]);
 
-  // Early return optimization
-  if (status == null || !status.isOpened) {
-    return <></>;
-  }
-
   return (
-    <Modal id="shortcuts-modal" size="lg" isOpen={status.isOpened} toggle={close} className={`shortcuts-modal ${styles['shortcuts-modal']}`}>
+    <>
       <ModalHeader tag="h4" toggle={close} className="px-4">
         {t('Shortcuts')}
       </ModalHeader>
       <ModalBody className="p-md-4 mb-3 grw-modal-body-style overflow-auto">
         {bodyContent}
       </ModalBody>
+    </>
+  );
+};
+
+/**
+ * ShortcutsModal - Container component (lightweight, always rendered)
+ */
+const ShortcutsModal = (): React.JSX.Element => {
+  const status = useShortcutsModalStatus();
+  const { close } = useShortcutsModalActions();
+
+  return (
+    <Modal id="shortcuts-modal" size="lg" isOpen={status?.isOpened ?? false} toggle={close} className={`shortcuts-modal ${styles['shortcuts-modal']}`}>
+      {status?.isOpened && <ShortcutsModalSubstance />}
     </Modal>
   );
 };

+ 31 - 8
apps/app/src/features/openai/client/components/AiAssistant/Sidebar/DeleteAiAssistantModal.tsx

@@ -15,8 +15,17 @@ export type DeleteAiAssistantModalProps = {
   onConfirm: () => void;
 };
 
-export const DeleteAiAssistantModal: React.FC<DeleteAiAssistantModalProps> = ({
-  isShown, aiAssistant, errorMessage, onCancel, onConfirm,
+/**
+ * DeleteAiAssistantModalSubstance - Presentation component (heavy logic, rendered only when isOpen)
+ */
+type DeleteAiAssistantModalSubstanceProps = {
+  errorMessage?: string;
+  onCancel: () => void;
+  onConfirm: () => void;
+};
+
+const DeleteAiAssistantModalSubstance: React.FC<DeleteAiAssistantModalSubstanceProps> = ({
+  errorMessage, onCancel, onConfirm,
 }) => {
   const { t } = useTranslation();
 
@@ -46,13 +55,8 @@ export const DeleteAiAssistantModal: React.FC<DeleteAiAssistantModalProps> = ({
     </>
   ), [errorMessage, onCancel, onConfirm, t]);
 
-  // Early return optimization
-  if (!isShown || aiAssistant == null) {
-    return <></>;
-  }
-
   return (
-    <Modal isOpen={isShown} toggle={onCancel} centered>
+    <>
       <ModalHeader tag="h5" toggle={onCancel} className="text-danger px-4">
         {headerContent}
       </ModalHeader>
@@ -62,6 +66,25 @@ export const DeleteAiAssistantModal: React.FC<DeleteAiAssistantModalProps> = ({
       <ModalFooter className="px-4 gap-2">
         {footerContent}
       </ModalFooter>
+    </>
+  );
+};
+
+/**
+ * DeleteAiAssistantModal - Container component (lightweight, always rendered)
+ */
+export const DeleteAiAssistantModal: React.FC<DeleteAiAssistantModalProps> = ({
+  isShown, aiAssistant, errorMessage, onCancel, onConfirm,
+}) => {
+  return (
+    <Modal isOpen={isShown} toggle={onCancel} centered>
+      {isShown && aiAssistant != null && (
+        <DeleteAiAssistantModalSubstance
+          errorMessage={errorMessage}
+          onCancel={onCancel}
+          onConfirm={onConfirm}
+        />
+      )}
     </Modal>
   );
 };