redux toolkit을 이용한 모달창 개발하기

seonik kim·2023년 8월 24일

문제 :
모달을 띄워 여러 스텝으로 정보를 받아야하는 상황이 있었습니다. 기존 코드는 useModal 형태로 각 컴포넌트에서 Dialog의 state를 관리하는 방식으로 개발되어 있었습니다. 기존 방식으로 진행시 props의 복잡도와 어려운 사용성으로 팀 내에서 코드 개선을 요청하였습니다.

목표 :
누구나 쉽게 사용할 수 있는 사용성, 불필요한 재선언, 컴포넌트 간 props의 복잡도 개선을 목표로 합니다.

해결방법 :
리덕스 툴킷을 이용해 전역으로 isOpen 상태를 관리하고, GlobalModal을 만들어 보여주고자 한 Dialog를 type형태로 받아 Component 형태로 출력한다.

리덕스 툴킷을 이용한 모달창 개발

목차
1. redux/modalSlice.ts에 필요한 값 정의
2. GlobalModal.tsx 생성
3. App.tsx 모달창 호출
4. type별 필요한 Dialog 생성
5. 사용방법

1. redux/modalSlice.ts에 필요한 값 정의

type : 띄우고자 하는 Dialog
isOpen : Modal 열림/닫힘 상태

import { PayloadAction, createSlice } from '@reduxjs/toolkit';

type ModalState = {
  type: string;
  isOpen: boolean;
};

const initialState: ModalState = {
  type: '',
  isOpen: false,
};

const modalSlice = createSlice({
  name: 'modal',
  initialState,
  reducers: {
    openModal: (state, action: PayloadAction<string>) => {
      state.type = action.payload;
      state.isOpen = true;
    },

    closeModal: (state) => {
      state.isOpen = false;
    },
  },
});

export default modalSlice.reducer;
export const { openModal, closeModal } = modalSlice.actions;

2. GlobalModal.tsx 생성

모든 Modal을 관리할 수 있는 GlobalModal.tsx을 생성합니다. 이 파일에서는 사용하고자하는 Dialog를 type으로 받아 component 형태로 출력하고자 합니다.

import * as S from './Modal.styled';
import { Alert, Report } from '..';
import { useSelector } from 'react-redux';
import { RootState } from '../../redux/config/configStore';
import ModalPortal from './ModalPortal';

const MODAL_TYPES = {
  basic: 'basic',
  alert: 'alert',
};

const MODAL_COMPONENTS = [
  {
    type: MODAL_TYPES.basic,
    component: <Report />,
  },
  {
    type: MODAL_TYPES.alert,
    component: <Alert />,
  },
];

const GlobalModal = () => {
  const { type, isOpen } = useSelector((state: RootState) => state.modal);
  if (!isOpen) return;

  const findModal = MODAL_COMPONENTS.find((modal) => {
    return modal.type === type;
  });

  const renderModal = () => {
    return findModal?.component;
  };


  return (
    <ModalPortal>
      <S.Overlay />
      {renderModal()}
    </ModalPortal>
  );
};

export default GlobalModal;
  • 고려사항
    createPortal을 사용해 Modal 오픈시 root에 있는 요소와 z-index 오류를 방지했습니다.

장점 :
1. 필요한 모달을 다시 정의할 필요없이 App에서 사용할 수 있다.
2. 모달을 띄울 때 사용되는 scrim영역(overlay)을 중복해서 사용하지 않아도 된다.


3. App.tsx 모달창 호출

<GlobalModal />을 모든 컴포넌트에서 렌더링 할 필요없이 App.tsx에서 렌더링합니다.

import GlobalModal from './components/modal/GlobalModal';
import Router from './shared/Router';
import GlobalStyle from './style/GlobalStyle';

function App() {
  return (
    <>
      <Router />
      <GlobalStyle />
      <GlobalModal />
    </>
  );
}

export default App;

이후 dispatch를 통해 원하는 Dialog type을 호출해주면 원하는 정보를 띄어줄 수 있게 됩니다.

장점 :
1. 모든 컴포넌트에서 상태관리를 할 필요가 없다.
2. 하위 컴포넌트에서 모달창을 열고 닫는 Props의 복잡도가 해결된다.


4. type별 필요한 Dialog 생성

서비스에서 사용할 Dialog 컴포넌트를 만들어서 사용합니다.

import { useDispatch } from 'react-redux';
import * as S from './Alert.styled';
import { closeModal, openModal } from '../../../redux/modules';

const Alert = () => {
  const dispatch = useDispatch();

  const handleClose = () => {
    dispatch(closeModal());
  };

  return (
    <S.Overlay>
      <S.Container>
        <S.Inner
          onClick={(e: React.MouseEvent<HTMLElement>) => {
            e.stopPropagation();
          }}
        >
          <button onClick={handleClose}>닫기</button>
        </S.Inner>
      </S.Container>
    </S.Overlay>
  );
};

export default Alert;

5. 사용방법

모달은 1가지 Dialog를 사용자에게 전달을 하기 때문에
리덕스에서 openModal(''), closeModal('') 2개의 dispatch 만으로도
멀티 스텝의 모달을 구현할 수 있습니다. (설문조사 형식으로 활용가능)

코드추가
profile
김선익

0개의 댓글