본문 바로가기
내일배움캠프 TIL

2024-01-29 본 캠프 82일차 / 101일차 TIL (custom confirm)

by KMS_99 2024. 1. 31.

2024-01-29 본 캠프 82일차 / 101일차 TIL (custom confirm)

 

custom confirm 구현

 

프로젝트의 UI를 고려하였을 때 기본 confirm을 사용하는 것 보단 원하는 디자인과 버튼 text를 구현할 수 있는 confirm을 만들 필요성이 있었다.

 

confirm을 만들기 위해서 고려해야할 사항이 몇가지 있었다.

1. 모든 페이지에서 사용이 가능해야한다.

2. 원하는 title이나 button text를 사용해야한다.

 

위와 같이 고려하였을 때 전역상태관리custom hook 화 하는 방안을 고려하였다.

 


1. 전역상태를 위한 confirmStore 구성

import { create } from "zustand";

export interface ConfirmTextState {
  title: string;
  message: string;
  denyButtonText?: string;
  confirmButtonText?: string;
}

interface ConfirmType extends ConfirmTextState {
  result: boolean;
  isOpen: boolean;
  openConfirm: ({
    title,
    message,
    confirmButtonText,
  }: ConfirmTextState) => void;
  closeConfirm: () => void;
  resultConfirm: (result: boolean) => void;
}

const initialState = {
  title: "",
  message: "",
  denyButtonText: "취소",
  confirmButtonText: "확인",
  result: false,
  isOpen: false,
};

const confirmStore = create<ConfirmType>()((set) => ({
  ...initialState,
  openConfirm: ({
    title,
    message,
    denyButtonText,
    confirmButtonText,
  }: ConfirmTextState) =>
    set(() => ({
      title,
      message,
      denyButtonText: denyButtonText ? denyButtonText : "취소",
      confirmButtonText: confirmButtonText ? confirmButtonText : "확인",
      isOpen: true,
    })),
  closeConfirm: () =>
    set(() => ({
      ...initialState,
    })),
  resultConfirm: (result: boolean) =>
    set(() => ({
      ...initialState,
      result,
    })),
}));

export default confirmStore;

 

confirm을 구현하기 위해서 필요한 상태는 다음과 같이 판단하였다.

1. title : confirm의 제목

2. message: confirm의 메시지

3. denyButtonText: 취소 버튼에 사용할 메시지

4. confirmButtonText: 확인 버튼에 사용할 메시지

5. result: 사용자가 확인, 취소 버튼 중 어느 버튼을 눌렀는가

6. isOpen: confirm이 열려있는가?

 

위 상태를 변경시키기 위한 action 함수는 다음과 같이 구성하였다

1. openConfirm: 컨펌 모달이 열리도록 하는 함수

2. closeConfirm: 컨펌 모달이 닫히도록 하는 함수

3. resultConfirm: 컨펌 결과를 반영하도록 하는 함수


2.  custum hook 제작 (useConfirm)

import confirmStore, { ConfirmTextState } from "@/zustand/confirmStore";

export default function useConfirm() {

  const {
    closeConfirm,
    isOpen,
    message,
    openConfirm,
    result,
    resultConfirm,
    title,
    confirmButtonText,
    denyButtonText,
  } = confirmStore();

  const closeConfirmHandler = () => {
    // 닫기 이벤트
    closeConfirm();
  };

  const openConfirmHandler = (alertInfo: ConfirmTextState) => {
    return new Promise((res) => {
      openConfirm(alertInfo);
      const unsubscribe = confirmStore.subscribe((state) => {
        const result = state.result;
        res(result);
        unsubscribe();
      });
    });
  };

  const setResult = (result: boolean) => {
    resultConfirm(result);
  };

  return {
    closeConfirmHandler,
    openConfirmHandler,
    setResult,
    isOpen,
    message,
    result,
    title,
    confirmButtonText,
    denyButtonText,
  };
}

custum hook을 구현함에 있어 중요했던 부분은 결과 도출을 위한 비동기 처리였다.

컨펌이 나타날 때 사용자는 확인, 취소 버튼을 클릭 전 다음 동작을 기다리는 처리를 해야한다.

따라서 openCOnfirmHandler를 실행할 때 Promise 객체를 생성한다.

 

이 때 결과를 도출하는 res콜백 함수에는 유저가 선택한 버튼의 정보(확인, 취소)가 들어간다.

 

이때 사용한 것이 zustand의 subscribe이다. 

 

특정 상태의 변화를 감지해주는 메서드로 result의 변화를 감지하면서 true, false가 될 때 res 함수가 실행되게 된다.

 

따라서 비동기 처리가 가능해지며, 사용자는 이후 동작을 조건부로 실행할 수 있게 된다.


3. 사용

  const handleLogout = async () => {
    const result = await openConfirmHandler({
      title: "로그아웃",
      message: " 정말 로그아웃 하시겠습니까?",
      confirmButtonText: "네, 로그아웃할게요",
      denyButtonText: "아니요, 더 둘러볼게요",
    });
    if (result) logout();
  };

대표적으로 confirm을 사용하는 logout 로직이다.

promise 객체를 반환하는 openConfirmHandler로 인해 async 함수를 생성하며, 각종 confirm 정보를 인자로 넘겨준다.

result에는 비동기 적으로 값이 들어오게 되며, 해당 result의 값에 따라서 이후 동작을 조건부 처리하도록 한다.