타입스크립트 + Context : 모달 팝업 구현

728x90
반응형

1. index.tsx

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import ReactDOM from 'react-dom/client';
import { RecoilRoot } from 'recoil';
import App from './App';
import './index.css';
import GlobalStyle from './styles/GlobalStyles';
import AuthContextProvider from './context/AuthContext';
import { ModalProvider } from './hooks/useModal';

const queryClient = new QueryClient();

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
  <QueryClientProvider client={queryClient}>
    <RecoilRoot>
      <AuthContextProvider>
        <ModalProvider>
          <App />
        </ModalProvider>
        <GlobalStyle />
      </AuthContextProvider>
    </RecoilRoot>
  </QueryClientProvider>
);

▶ModalProvider <App/> 컴포넌트 위에 작성 => hooks/ useModal에서 만든 provider

 

2. useModal.tsx

import { createContext, useContext, useState } from 'react';
import Modal from '../components/common/modal/Modal';

type ModalContextValue = {
  open: (params: OpenModalParams) => void;
  close: () => void;
};

export type OpenModalParams = {
  title: string;
  message?: string;
  leftButtonLabel?: string;
  onClickLeftButton?: () => void;
  rightButtonLabel?: string;
  onClickRightButton?: () => void;
};

const initialValue: ModalContextValue = {
  open: () => {},
  close: () => {}
};

const ModalContext = createContext(initialValue);

export function ModalProvider({ children }: { children: React.ReactNode }) {
  const [modalElement, setModalElement] = useState<React.ReactNode>(null);

  const open = (params: OpenModalParams) => {
    setModalElement(<Modal {...params} />);
  };

  const close = () => setModalElement(null);

  const modal: ModalContextValue = {
    open,
    close
  };

  return (
    <ModalContext.Provider value={modal}>
      {children}
      {modalElement}
    </ModalContext.Provider>
  );
}

export const useModal = () => useContext(ModalContext);

 

(1) ModalContextValue

=> 모달 컨텍스트에서 사용할 메소드와 속성을 포함한 타입을 정의

=> open함수: OpenModalParams라는 파라미터 받음

 

(2) OpenModalParams

=> 모달을 열 때 필요한 다양한 설정한 담고 있는 객체 (제목,메시지, 버튼 라벨 및 클릭 핸들러 설정)

 

(3) initialValue 및 ModalContext 생성

=> initialValue는 초기 모달 컨텍스트 값 정의

=> createContext 함수를 사용하여 모달 컨텍스트 생성

(= 이 컨텍스트는 다른 컴포넌트에서 모달 관련된 함수와 데이터에 접근 가능하도록 함)

 

(4) ModalProvider 

=> 모달 컨텍스트를 제공하는 컴포넌트 (자식 컴포넌트를 감싸는 역할)
=> modalElement는 모달 컴포넌트를 저장하는데에 사용

 

(5) open

=> OpenModalParams를 받아 모달 컴포넌트를 생성하고 modalElement 변수에 저장

=> close는 null로 설정하여 모달 닫기

 

(6) modal 객체

=> ModalContext.Provider의 value로 제공

( ModalContext.Provider: 모달 컨텍스트를 자식 컴포넌트에 제공함)

( value를 props로 전달하는데, modal 객체를 전달함)

 

(7) useModal 커스텀 훅

=> useContext 훅을 사용해서 모달 컨텍스트를 가져옴 / 이를 통해 다른 컴포넌트에서 모달을 열고 닫기 가능

 

3. Modal.tsx

: 모달 프레임

import React from 'react';
import St from '../modal/style';
import { useRecoilValue } from 'recoil';
import { OpenModalParams } from '../../../hooks/useModal';

type ModalProps = Exclude<OpenModalParams, 'closeButton'>;

function Modal(props: ModalProps) {
  const { title, message, leftButtonLabel, onClickLeftButton, rightButtonLabel, onClickRightButton } = props;

  return (
    <>
      <St.ScDiv>
        <St.ScDivContainer>
          <St.ScDivTitleAndContent>
            <h2>{title}</h2>
            <p>{message}</p>
          </St.ScDivTitleAndContent>

          <St.ScDivButton>
            {leftButtonLabel && <St.ScButtonFirst onClick={onClickLeftButton}>{leftButtonLabel}</St.ScButtonFirst>}
            {rightButtonLabel && <St.ScButtonSecond onClick={onClickRightButton}>{rightButtonLabel}</St.ScButtonSecond>}
          </St.ScDivButton>
        </St.ScDivContainer>
      </St.ScDiv>
    </>
  );
}

export default Modal;

(1) ModalProps :타입

=> OpenModalParams에서 'closeButton' 속성을 제외하고 사용하기 위한 타입

 

(2) Modal 컴포넌트 

=> props로 ModalProps 타입을 받아와서 해당 모달에 표시

 

 

4. EditNDeleteToggle.tsx

: 다른 컴포넌트에서 모달 컴포넌트 가져와서 사용하기

import { useModal } from '../../hooks/useModal';

function EditNDeleteToggle({ foundPost }: FoundPostProps) {
  const modal = useModal();
  const queryClient = useQueryClient();

  const deleteMutation = useMutation({
    mutationFn: deletePost,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.POSTS] });
      navigate('/');
    }
  });

  const onDeletePostHandler = () => {
    const onClickCancel = () => {
      modal.close();
    };

    const onClickSave = () => {
      deleteMutation.mutate(foundPost.id);
      modal.close();
    };

    const openModalParams: Parameters<typeof modal.open>[0] = {
      title: '삭제 하시겠습니까?',
      message: '',
      leftButtonLabel: '취소',
      onClickLeftButton: onClickCancel,
      rightButtonLabel: '삭제',
      onClickRightButton: onClickSave
    };
    modal.open(openModalParams);
  };

  return (
    내용내용내용 onDeletePostHandler 실행~~
  );
}

export default EditNDeleteToggle;

 

(1) useModal()을 가져와서 사용하기 

(2) 필요한 곳에, modal.open(openModalParams)로 타입정의 + 파라미터 생성 

* typeof modal.open = modal객체의 'open'메소드 가져오기 //(modal.open은 함수임)

* Parameters<typeof modal.open>

:Parameters 제네릭타입 = 함수의 파라미터 타입들을 튜플 형태로 가져오는 typeScript내장함수

: typeof  사용 이유: 함수 타입을 추출할때, 함수의 이름과 함께 해당 함수의 타입 가져오기

*[0] 

: Parameters<typeof modal.open>에서 추출한 파라미터 타입 중 첫번째 (인덱스 0) 파라미터 타입 선택함

 

** 버튼을 하나만 사용하고 싶으면 onClickLeftButton을 undefined로 설정해서 사용하기

 

 

끝.

반응형