electron에서 alert창 사용 시 input focus 잃는 이슈를 모달창으로 해결

2023. 7. 3. 14:52프론트엔드

외주개발 하면서 별 문제가 아니라고 생각했던 문제가 드디어 직면 했다. 바로 electron에서 input 포커싱을 잃는 큰 문제 였다.

아무리… 찾아 봐도 electron을 한국에서 쓰지 않는지라 관두고 있었는데 영어로 검색하니까 바로 나왔다.. 역시 영어를 배워야 한다..

 

*추가: electron에는 alert대신 dialog api를 사용해서 해결 할 수 있다.

https://www.electronjs.org/docs/latest/api/dialog

 

관련 이슈

Can't edit input text field after window.alert()

 

Can't edit input text field after window.alert()

I've got this Electron app (using NodeJS, Bootstrap, AngularJS) with some text input fields that can be edited. I have a button that triggers a window.alert() After it has been triggered, the text ...

stackoverflow.com

애초에 electron에서는 alert창을 사용하는것을 권장하지 않았다. 내가 겪은 문제는..

  • electron에서 alert(알림창)를 사용하면 alert창에 포커싱이 간다.
  • 여기서 확인을 눌러 버리면 alert창에 유지 되던 포커싱이 input창에 돌아오지 않는 버그 발생
  • 해결 하려면 다른 윈도우 창을 클릭해서 포커스를 되찾아 와야 함…

따라서 알림 창을 modal 창으로 줘서 이를 해결해 보고자 함

antd의 modal 컴포넌트를 참고 해서 코드를 작성 했다.

import { Modal } from "antd";

const AlertModal = ({ title, state, setIsModalOpen }) => {
  const handleOk = () => {
    state.handler();
    setIsModalOpen(false);
  };

  const handleCancel = () => {
    setIsModalOpen(false);
  };

  return (
    <>
      {state.open && (
        <Modal title={title} open={state.open} onOk={handleOk} type="success">
          <p>{state.message}</p>
        </Modal>
      )}
    </>
  );
};

export default AlertModal;

이를 로그인 로직 끝에 추가 했다.

기존 코드

alert(`로그인 결과 : ${result.message}`);

모달창을 적용한 코드

const [modal, setOpen] = useState({ open: false, message: "", handler:()=>{});

//로그인 로직
setOpen({
    open: true,
    message: "로그인 성공",
    handler: () => {
      //로그인 성공 시 함수 동작
      });
    }
});

//

위와 같이 작성하고, 컴포넌트를 추가 한다.

<AlertModal
  title="로그인 결과"
  state={modal}
  setIsModalOpen={setOpen}
/>

와 해결했다..!! 하지만…

모달창 상태를 커스텀 훅으로 모듈화 해서 사용하기

정상적으로 다 작동은 하지만, alert를 박아 둔곳이 많아서 이를 모듈화 해서 재사용성을 높이고자 한다. 

 

useModalState.js

import { useState } from "react";
import AlertModal from "../../component/AlertModal";

const useModalState = (title) => {
  const [modal, setOpen] = useState({ open: false, message: "" ,handler:()=>{}});

  const openModal = (message, handler) =>
    setOpen({
      open: true,
      message: message,
      handler,
    });

  return {
    MyModal: () => (
      <AlertModal title={title} state={modal} setIsModalOpen={setOpen} />
    ),
    openModalFunc: openModal,
  };
};

export default useModalState;
  • 반복 적으로 사용되는 모달과 모달이 닫힌 후 동작하는 행동을 정의할 수 있는 함수를 같이 반환 한다.

위와 같이 쓰고 로그인 로직에서 코드를 고쳐 준다.

//타이틀을 정해준다.
//MyModal: 모달 컴포넌트
//openModalFunc: 모달을 오픈할 때의 설정을 지정하는 함수
const { MyModal, openModalFunc } = useModalState("로그인 결과");

//...
openModalFunc("로그인 성공", () => {
  //로그인 성공 했을 때의 동작
    );
});

//
return <>
    <MyModal />
</>

굳이 모달의 상태를 객체로 관리 하지 않아도 간편하게 알아볼 수 있게 모달을 설정할 수 있다.

모듈로 분리 했으니… 여기저기 리팩토링 해야겠다.. 그럼 이만!

 

라고했습니다. 이제 새로운 이슈와 함께 리펙토링을 해야할 때가 온것입니다.

7.01 Modal 창 Cancel 키 먹통 이슈

Modal 관련 에러 ( emp ) #issue/Modal 

  • 문제 : 업무 기록 제출 시 dialog 창에 취소 버튼을 눌러도 데이터 전송 및 form 데이터 조작 불가 현상

useModalState.js 커스텀 훅 작성

import { useState } from "react";
import AlertModal from "../component/modal/AlertModal";

const initialState = {
  open: false,
  message: "",
  okHandler: () => {},
  cancelHandler: () => {},
};

const useModalState = (title) => {
  const [state, setIsOpen] = useState(initialState);

  const openModal = ({
    message,
    okHandler = () => {},
    closeHandler = () => {},
  }) =>
    setIsOpen({
      open: true,
      message,
      okHandler,
      closeHandler,
    });

  const closeModal = () => {
    setIsOpen((prev) => {
      return { ...prev, open: false };
    });
  };

  return {
    ModalElement: () => (
      <AlertModal
        title={title}
        isOepn={state.open}
        message={state.message}
        setCloseModal={closeModal}
        handleOpen={state.okHandler}
        handleClose={state.cancelHandler}
      />
    ),
    openModalWithSetting: openModal,
  };
};

export default useModalState;

AlertModal.js 컴포넌트

const AlertModal = ({
  title,
  isOepn,
  setCloseModal,
  message,
  handleOpen = () => {},
  handleClose = () => {},
}) => {
  const [confirmLoading, setConfirmLoading] = useState(false);

  const handleOk = async () => {
    setConfirmLoading(true);
    await handleOpen();
    setConfirmLoading(false);
    setCloseModal();
  };

  const handleCancel = async () => {
    setConfirmLoading(true);
    await handleClose();
    setConfirmLoading(false);
    setCloseModal();
  };

  return (
    <>
      <Modal
        title={title}
        open={isOepn}
        onOk={handleOk}
        confirmLoading={confirmLoading}
        onCancel={handleCancel}
      >
        <p>{message}</p>
      </Modal>
    </>
  );
};

export default AlertModal;
  • OK 상황과 , CANCEL상황을 비동기로 처리할 수 있는 모달창 작성

모달 리펙토링 효과

원래의 코드(리펙토링 전)

//타이틀을 정해준다.
//MyModal: 모달 컴포넌트
//openModalFunc: 모달을 오픈할 때의 설정을 지정하는 함수
const { MyModal, openModalFunc } = useModalState("로그인 결과");

//...
openModalFunc("로그인 성공", () => {
  //로그인 성공 했을 때의 동작
    );
});

//
return <>
    <MyModal />
</>

리펙토링 후

const { ModalElement, openModalWithSetting } = useModalState("업무기록제출");
...
openModalWithSetting({
  message: "업무기록 제출을 완료하였습니다.",
  okHandler: () => {
    form.resetFields();
    refreshHandler();
    setDisabled(false);
  },
});
  • 객체로 매개변수를 받아, 각각 변수의 역할을 쉽게 알 수 있다. 이로인해 가독성이 높아지고, 유지보수가 용이해짐
  • OK와 Cancel의 작업을 분리해서 비동기 작업을 처리 가능하게 됐음. 확실하게 Cancel 버튼을 누르면 작업이 취소됨.