[FIFAPulse] 개발기록 - 전역으로 모달 창 구현하기

조민호·2023년 4월 24일
0
post-custom-banner

모달창을 사용해보자



피파온라인 API는 친절하게도 닉네임을 기반으로 유저의 고유 ID를 반환해주고 있다


이렇게 전달받은 accessId를 기반으로 앞으로 파피온라인 유저의 정보들을 검색할 수 있는 것이다


그렇다면 이걸 내 프로젝트에 활용해야 하는데
처음 로그인을 할 때 , 피파온라인에서 사용하고 있는 실제 ID를 입력받아서 이 웹에서 사용하는 구글 계정과 피파온라인 계정을 연결하고자 했다

  • 최초로 로그인을 한다
  • (만약 DB에 해당 구글계정의 로그인 정보가 없을 경우 )피파온라인 닉네임을 입력한다
  • 해당 닉네임을 바탕으로 API를 요청해서 accessId와 함께 저장한다

이런 과정을 거치는 것이다!



다만 , 이런 과정을 굳이 별도의 페이지로 넘기기엔 UX측면에서 피로감이 쌓일 것이라 생각이 들었고 그래서 모달창으로 구현하는 것이 좋을거라 생각했다

모달창 구현하는 방법은 정말 여러가지가 있다

  1. 전역 상태(context , zustand 등)을 이용해서 전역으로 관리하는 모달창을 직접 구현

  2. 모달이 필요한 페이지에 하위 컴포넌트로 모달 컴포넌트를 작성하고 css로 처리하기

  3. ReactDOM.createPortal(child, container) 을 사용해서 id=root인 div말고

    외부에 모달창 생성하기 (이것도 전역과 비슷해 보인다)

  4. 리액트 라이브러리 (react-modal) 사용

  5. 부트스트랩 사용



이전 프로젝트에는 부트스트랩을 사용했었고 기존에 아는 방식은 2번 방식 뿐이었다

그렇지만 지금 프로젝트는 무엇보다 기능 구현보단 , 코드의 품질에도 신경을 써보고 싶어서

라이브러리의 힘을 빌린다기보다 직접 스스로 구현을 해보기로 했다


그러던 도중 모달창은 언제 어디서 사용될 지 모르니 일일이 필요할 때마다 하위 컴포넌트로 만들어서 구현하는 것보다 ,
전역으로 관리하는게 좋다는 주변 지인의 말에 첫번째 방법을 사용해보게 되었다


  • 전역 상태로 context를 사용해도 되고
  • 상태관리 라이브러리인 redux , zustand 등을 사용해도 되지만

모달 창 자체가 사실 상태로써 자주 값이 변경되며 지속적인 관리가 필요한 것은 아닌 ,
단지 일회적인 용도로만 사용된다고 생각이 들어서 굳이 상태관리 라이브러리가 아닌 context로 해결하고자 했다



context로 전역 모달창 구현하기

  1. 모달 관련 context를 생성합니다

    import React, { createContext, useContext, useState } from 'react';
    
    interface ModalContextValue {
      isModalOpen: boolean;
      openModal: (children: React.ReactNode) => void;
      closeModal: () => void;
    }
    
    export const ModalContext = createContext<ModalContextValue | null>(null);
    
    export const ModalProvider = ({ children }: { children: React.ReactNode }) => {
      const [isModalOpen, setIsModalOpen] = useState(false);
      const [modalContents, setModalContents] = useState((<></>) as React.ReactNode);
    
      const openModal = (children: React.ReactNode) => {
        setIsModalOpen(true);
        setModalContents(children);
      };
    
      const closeModal = () => setIsModalOpen(false);
    
    	// 모달 바깥 영역을 클릭하면 모달이 닫힙니다
      const onDimmerClick = (event: React.MouseEvent<HTMLDivElement>) => {
        if (event.currentTarget !== event.target) return;
    
        closeModal();
      };
    
      return (
        <ModalContext.Provider value={{ isModalOpen, openModal, closeModal }}>
          {children}
          {isModalOpen && <div onClick={onDimmerClick}>{modalContents}</div>}
        </ModalContext.Provider>
      );
    };
    
    export function useModal() {
      return useContext(ModalContext);
    }

    여기서 사용된 Provider 부분에 대한 설명은 아래와 같다

     <ModalContext.Provider value={{ isModalOpen, openModal, closeModal }}>
          {children}
          {isModalOpen && <div onClick={onDimmerClick}>{modalContents}</div>}
    </ModalContext.Provider>
    • ModalContext의 모든 children컴포넌트들 에서는 useModal()을 통해 value로 지정한 isModalOpen, openModal, closeModal 사용이 가능하다
      모든 하위 컴포넌트에서 모달을 사용하기 위한 함수들을 넘겨주는 것이다


      a. isModalOpen 의 상태에 따라 모달을 띄우고 닫는다
      b. openModal() 는 모달의 상태를 바꿔주고 인자로 넘겨받은 jsx코드로 모달창을 생성한다
      c. closeModal()은 모달의 상태를 바꿔줘서 모달을 닫는다

    • isModalOpen 이 true라면 div로 된 모달창을 띄운다 이 모달창은 {children}처럼 자식 컴포넌트 어딘가에 존재하는 것이 아니라 Provider로 감싸고 있는 현재 위치에 존재하는 것이 된다 즉, ModalContext.Provider가 있는 최상위에 전역으로 존재하는 컴포넌트인 것이다

    자식 컴포넌트 어딘가에서 useModal()로 부터 받은 함수를 사용해서 모달을 생성하게 되면
    ModalContext.Provider가 있는 최상위에 컴포넌트에 모달이 생성되는 것이다

  2. contextAPI를 이용하여 해당 컴포넌트에서 모달을 생성한다

    import React, { useEffect, useState } from 'react';
    import { useModalAPI } from '../../Context/Modal/ModalContext';
    import AskNickName from '../../Components/AskNickName';
    
    const ChooseModeAndLogin = () => {
     
    	...
    
      const { isModalOpen, openModal } = useModalAPI()!;
    
       const onSocialClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
    
    		...
    
        // 모달을 열기
        openModal(<AskNickName />);
      };
    
      return (
      <div>
    
    			...
    
          <button name="google" onClick={onSocialClick}>
            로그인 하기(Google)
          </button>
    
        </div>
      );
    };
    
    export default ChooseModeAndLogin;
post-custom-banner

0개의 댓글