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로 설정해서 사용하기
끝.
'TIL :: Today I Learned' 카테고리의 다른 글
타입스크립트 + useInfiniteQuery : 좋아요 기능 구현 (Optimistic Update) (0) | 2024.01.20 |
---|---|
타입스크립트 + useInfiniteQuery : 더보기 기능 마지막 데이터 확인 (0) | 2024.01.18 |
react-spinners : 로딩스피너 구현 (0) | 2024.01.16 |
Styled-components: GlobalSytles.tsx에 공용 폰트 적용 (0) | 2024.01.12 |
타입스크립트 + useInfiniteQuery : 정렬 기능 구현 (0) | 2024.01.11 |