상세 컨텐츠

본문 제목

React Modal 구현하기 (Alter, Loading)

Front/React

by Chan.94 2025. 2. 8. 08:00

본문

반응형

모달(Modal)과 팝업(Popup)

Popup

현재 열려있는 브라우저 페이지에 또 다른 브라우저 페이지를 띄우는 것

Modal

일반 팝업처럼 새로운 윈도우를 여는 것이 아닌 현재 윈도우의 레이어를 쌓아서 보여주는 형식의 팝업을 의미

  • 사용자의 이목을 끌기 위해 표현하는 화면 전환 기법
  • 화면 전환 보다는 이목을 집중해야 하는 화면을 다른 화면 위로 띄워 표현하는 방식
    중요한 정보 전달 혹은 사용자에게 작업에 대한 내용 확인
  • 팝업처럼 새로운 창을 띄우는 게 아니라, 기존 열려있는 브라우저 위에 레이어 깔기
    기존 브라우저와 부모-자식 형태의 관계
  • 모달 창을 사라지게하려면 반드시 특정 선택을 해야 함.⇒ 다음 단계로 나아가기 위해 필요한 창
  • 브라우저 옵션에 영향을 받지 않음

공통점

  • 특정 영역 위치에 원하는 사이즈로 별도의 레이어를 만들어서 사용자에게 어필한다. 프로모션 진행, 서비스 공지, 안내문 등을 전달 또는 강조할 때 사용한다.

차이점

  • 팝업창의 경우 웹 시작과 동시에 띄우는 경우가 많다 / 모달창의 경우 중간중간에 사용자에게 보여지는 경우가 많다
  • 팝업창은 현재 의도하는 목적과 상관없이 뜨는 창이고 / 모달창은 다음 진행으로 넘어가기 위한 창이다.
  • 모달창을 닫기 전에는 부모창에 직접 이벤트를 행할 수 없고 / 모달창은 제어가 자유롭다.

Alert Modal 구현

Modal.js

import React from "react";
import "./Modal.css";

const Modal = ({ isOpen, onClose, type = "A", children, onConfirm }) => {
  if (!isOpen) return null; // 모달이 열리지 않은 경우 렌더링하지 않음

  return (
    <div className="modal-overlay" >
      <div className="modal-content" >
        {type === "A" && (
          <>
            <div className="div-alert">Alert</div>
            <p>{children}</p>
            <button className="btn-ok" onClick={() => { onConfirm?.(); onClose(); }}>OK</button>
          </>
        )}
        {type === "C" && (
          <>
            <div className="div-confirm">Confirm</div>
            <p>{children}</p>
            <button className="btn-ok" onClick={() => { onConfirm?.(); onClose(); }}>Yes</button>
            <button className="btn-cancle" onClick={onClose}>No</button>
          </>
        )}
        {type === "E" && (
          <>
            <div className="div-error">Error</div>
            <p>{children}</p>
            <button className="btn-cancle" onClick={() => { onConfirm?.(); onClose(); }}>OK</button>
          </>
        )}
      </div>
    </div>
  );
};

export default Modal;
  • type에 따라 modal-content 영역을 동적으로 그리도록 분기처리함.
    A - Alert
    C - Confirm
    E - Error

Modal.css

더보기
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 1000;
}
  
.modal-content {
    background: white;
    border-radius: 8px;
    max-width: 500px;
    width: 100%;
    position: relative;
    text-align: center;
}
  
.modal-content .div-alert {
    border-radius: 5px;
    background: gainsboro;
}
.modal-content .div-confirm {
    border-radius: 5px;
    background: gainsboro;
}
.modal-content .div-error {
    border-radius: 5px;
    background: red;
}
  
.modal-content p {
    padding: 20px;
    margin: 10px 0 20px;
}
  
.modal-content button {
    margin: 5px;
    padding: 8px 15px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}
  
.modal-content .btn-ok {
    background-color: #0800ff;
    color: white;
}
  
.modal-content .btn-cancle {
    background-color: #f44336;
    color: white;
}

ModalContext.js

import React, { createContext, useContext, useState } from "react";
import Modal from "../components/modal/Modal";

const ModalContext = createContext();
let globalOpenModal = null; // 전역 모달 호출 함수

export const ModalProvider = ({ children }) => {
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [modalType, setModalType] = useState(null); // alert, confirm, error 등
  const [modalContent, setModalContent] = useState(null);
  const [onConfirm, setOnConfirm] = useState(null); // confirm 모달용 콜백
  
  const openModal = (type, content, onConfirmCallback) => {
    setModalType(type);
    setModalContent(content);
    setOnConfirm(() => onConfirmCallback || null);
    setIsModalOpen(true);
  };

  const closeModal = () => {
    setIsModalOpen(false);
    setModalType(null);
    setModalContent(null);
    setOnConfirm(null);
  };

  // 전역에서 사용할 수 있도록 설정
  globalOpenModal = openModal;
    
  //prop으로 { openModal, closeModal } 객체를 전달하여 하위 컴포넌트에서 이 값을 사용할 수 있게 한다
  return (
    <ModalContext.Provider value={{ openModal, closeModal }}>
      {children}
      {/* 모달 렌더링 */}
      <Modal
        isOpen={isModalOpen}
        onClose={closeModal}
        type={modalType}
        onConfirm={onConfirm}
      >
        {modalContent}
      </Modal>
    </ModalContext.Provider>
  );
};

//useModal은 커스텀 Hook으로, useContext를 사용하여 ModalContext를 가져온다. 이를 통해 하위 컴포넌트에서 Modal관련 정보에 쉽게 접근할 수 있다
export const useModal = () => useContext(ModalContext);

// 전역 모달 호출 함수
export const getGlobalModal = () => globalOpenModal;
  • useContext Hook을 사용하여 자식 컴포넌트에게 openModal, closeModal 함수를 prop로 전달한다.
  • useModal이라는 Custom Hook을 만들어 전달한다.
  • 자식 컴포넌트에서 openModal함수를 호출하면 결과적으로 Modal 컴포넌트에 isOpen값이 true가 되어 Modal창이 화면에 출력된다.
import React from 'react';
import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom';
import CombinedProvider from './context/CombinedProvider';
import MainPage from './pages/MainPage';

function App() {
  return (
    <CombinedProvider>
      <BrowserRouter>
        <Routes>
          <Route path="/main" element={<MainPage />} />
        </Routes>
      </BrowserRouter>
    </CombinedProvider>
  );
}

export default App;

생성한 Context를 애플리케이션의 최상위 컴포넌트로 설정하여 하위 컴포넌트가 Modal관련 정보에 쉽게 접근하고 사용할 수 있음

 

CombinedProvider.js

Context 여러개를 구현한 후 한 개의 Provider로 관리한다.

더보기
import React from "react";
import { AuthProvider } from "./AuthContext";
import { ModalProvider } from "./ModalContext";

const CombinedProvider = ({ children }) => {
    return (
        <ModalProvider>
            <AuthProvider>
                {children}
            </AuthProvider>
        </ModalProvider>
    );
};

export default CombinedProvider;

axios에 Modal 적용

axios.js

import axios from 'axios';
import { getGlobalModal } from '../context/ModalContext';

// Axios 인스턴스 생성
const axiosInstance = axios.create({
...
});

// 요청 인터셉터
axiosInstance.interceptors.request.use(
...
);

// 공통 요청 함수
const axiosRequest = async (method, url, data = null, fn_callback, bResponseOnlyData = true) => {

    try {
        ...
    } catch (error) {
        fn_axiosError(error);
    }
};

const fn_axiosError = async(error) => {

    const openModal = getGlobalModal();
    const originalRequest = error.config;

    // 기타 에러 처리
    console.log(error);
    openModal("E", `${error.message}`);
}

export default axiosRequest; // 기본 익스포트 사용

 


Loading Modal 구현

Loading Modal은 화면에서 API 요청을 보낸 이후 API 요청을 받기 전까지 화면에서 동작하지 못하도록 사용자를 제어할 수 있다.

React-Spinners 라이브러리를 사용하여 쉽게 구현할 수 있다.

React-Spinners 라이브러리 설치

npm install react-spinners

https://www.davidhu.io/react-spinners/

 

React Spinners

yarn add react-spinners

www.davidhu.io

원하는 모양을 선택

 

LoadingModal.js

import React from 'react';
import "./LoadingModal.css";
import { RingLoader } from "react-spinners";

const LoadingModal = () => (
  <div className="modal-overlay">
    <RingLoader color="#36D7B7" 
                size={50}
                />
  </div>
);

export default LoadingModal;

 

LoadingModal.css

.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 1000;
}

 

ModalContext.js 수정

import React, { createContext, useContext, useState } from "react";
import Modal from "../components/modal/Modal";
import LoadingModal from "../components/modal/LoadingModal";

const ModalContext = createContext();
let globalOpenModal = null; // 전역 모달 호출 함수
let globalLoadingModal = null; // 전역 로딩 모달 호출 함수

export const ModalProvider = ({ children }) => {
    ...
    const [isLoadingVisible, setIsLoadingVisible] = useState(false); // 로딩 모달 상태

    const openModal = (type, content, onConfirmCallback) => {
        ...
    };

    const closeModal = () => {
        ...
    };

    const applyLoadingModal = (isOpen) => {
        setIsLoadingVisible(isOpen);
    };

    // 전역에서 사용할 수 있도록 설정
    globalOpenModal = openModal;
    globalLoadingModal = applyLoadingModal;

    return (
        <ModalContext.Provider value={{ openModal, closeModal }}>
            ...
            {/* 로딩 모달 렌더링 */}
            {isLoadingVisible && <LoadingModal />}
        </ModalContext.Provider>
    );
};

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

// 전역 모달 호출 함수
export const getGlobalModal = () => globalOpenModal;

// 전역 로딩 모달 제어 함수
export const getGlobalLoadingModal = () => globalLoadingModal;

 


axios에 Loading Modal 적용

axios.js

import { getGlobalModal, getGlobalLoadingModal } from '../context/ModalContext';

...

// 공통 요청 함수
const axiosRequest = async (method, url, data = null, fn_callback, bResponseOnlyData = true) => {

    const loadingModal = getGlobalLoadingModal();
    loadingModal(true);

    try {
        ...
    } catch (error) {
        fn_axiosError(error);
    }
    
    loadingModal(false);
};

const fn_axiosError = async(error) => {

    const loadingModal = getGlobalLoadingModal();
    
    ...
    
    loadingModal(false);
}

api 호출 전후로 loadingModal 관리 함수 호출로직 추가

 


마무리

Alert Modal과 Loading Modal을 구현하였다. 이제부터는 javascript에서 제공하는 alert가 아닌 개발한 Modal을 사용도록 하자.

 

이전글

다음 글

  •  

 

반응형

'Front > React' 카테고리의 다른 글

React 로그인 구현 (JWT)  (2) 2025.02.09
React axios 모듈화  (0) 2025.02.07
React-Router-Dom 정리  (0) 2025.02.06
React Hook 기본 정리 / Custom Hook  (1) 2025.02.05
React CORS 설정 - 프록시 (http-proxy-middleware)  (7) 2025.01.31

관련글 더보기

댓글 영역

>