Skip to content

Commit

Permalink
Merge pull request #16 from Wannabe-Woowa-Article/lurgi
Browse files Browse the repository at this point in the history
  • Loading branch information
brgndyy authored Jul 5, 2024
2 parents 5ef483e + d7ebb82 commit 1b14da6
Showing 1 changed file with 278 additions and 0 deletions.
278 changes: 278 additions & 0 deletions June/article/How-to-handle-multiple-modals-in-a-React-application.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
## πŸ”— [How to handle multiple modals in a React application](https://dev.to/zettadam/how-to-handle-multiple-modals-in-a-react-application-2pei)

### πŸ—“οΈ λ²ˆμ—­ λ‚ μ§œ: 2024.06.29

### 🧚 λ²ˆμ—­ν•œ 크루: 러기(λ°•μ •μš°)

---

## λ¦¬μ•‘νŠΈ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ—¬λŸ¬κ°œμ˜ λͺ¨λ‹¬λ“€μ„ κ΄€λ¦¬ν•˜λŠ” 방법

μ°Έκ³ : 전체 예제 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ μ—¬κΈ°μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€: https://stackblitz.com/edit/react-modals

React μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ λͺ¨λ‹¬μ„ κ΄€λ¦¬ν•˜λŠ” 방법은 ν•˜λ‚˜λ§Œ μžˆλŠ” 것이 μ•„λ‹ˆμ§€λ§Œ, λͺ‡λͺ‡ 방식이 λ‹€λ₯Έ 방식보닀 λ‚˜μ„ 수 μžˆμŠ΅λ‹ˆλ‹€. 이 κΈ€μ—μ„œλŠ” Redux μŠ€ν† μ–΄ 같은 κΈ€λ‘œλ²Œ μŠ€ν† μ–΄λ₯Ό μ‚¬μš©ν•˜μ—¬ λͺ¨λ‹¬μ„ κ΄€λ¦¬ν•˜λŠ” 것보닀 더 κ°„λ‹¨ν•œ 방법을 μ†Œκ°œν•˜κ³ μž ν•©λ‹ˆλ‹€. 이 μ˜ˆμ œμ—μ„œλŠ” μ»΄ν¬λ„ŒνŠΈ μƒνƒœμ™€ 이벀트 버블링을 μ‚¬μš©ν•  것이며, μ΄λŠ” React의 Portals도 μ–ΈκΈ‰λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ‹¬μ€ 보톡 λΌμš°ν„°μ— μ˜ν•΄ κ΄€λ¦¬λ˜λŠ” λ³„λ„μ˜ ν™”λ©΄κ³Ό λΉ„μŠ·ν•œ 뢀뢄이 μžˆμŠ΅λ‹ˆλ‹€.

## AppShell

예λ₯Ό λ“€μ–΄ λ‹€μŒ src/AppShell.jsx μ˜ˆμ œμ™€ 같이, 두 μ’…λ₯˜μ˜ μ»΄ν¬λ„ŒνŠΈλ₯Ό 쀑앙 μ»΄ν¬λ„ŒνŠΈμ—μ„œ μ„œλ‘œ κ°€κΉκ²Œ λ Œλ”λ§ν•˜λŠ” 것이 합리적일 수 μžˆμŠ΅λ‹ˆλ‹€.

```tsx
import React, { useState } from 'react'
import { BrowserRouter, NavLink, Route, Switch } from 'react-router-dom'

import ScreenOne from './components/screen-one/ScreenOne'
import ScreenTwo from './components/screen-two/ScreenTwo'
import ScreenThree from './components/screen-three/ScreenThree'

import ModalOne from './components/common/modal-one/ModalOne'
import ModalTwo from './components/common/modal-two/ModalTwo'
import ModalThree from './components/common/modal-three/ModalThree'

import './app-shell.css'

const AppShell = () => {
const [modalOpen, setModal] = useState(false)

const openModal = event => {
event.preventDefault()
const { target: { dataset: { modal }}} = event
if (modal) setModal(modal)
}

const closeModal = () => {
setModal('')
}

return (
<BrowserRouter>
<div className="app--shell" onClick={openModal}>

{/* Application header and navigation */}
<header className="app--header">
<h1>React Modal Windows</h1>
<nav className="app--nav">
<NavLink to="/screen-one">Screen One</NavLink>
<NavLink to="/screen-two">Screen Two</NavLink>
<NavLink to="/screen-three">Screen Three</NavLink>
</nav>
</header>

{/* Application screens */}
<Switch>
<Route path="/screen-three">
<ScreenThree />
</Route>
<Route path="/screen-two">
<ScreenTwo />
</Route>
<Route path="/screen-one">
<ScreenOne />
</Route>
<Route exact path="/">
<ScreenOne />
</Route>
</Switch>

{/* Modals */}
<ModalOne
closeFn={closeModal}
open={modalOpen === 'modal-one'} />

<ModalTwo
closeFn={closeModal}
open={modalOpen === 'modal-two'} />

<ModalThree
closeFn={closeModal}
open={modalOpen === 'modal-three'} />

{/* Application footer */}
<footer className="app--footer">
<p className="copyright">&copy; 2021 Some Company</p>
</footer>

</div>
</BrowserRouter>
```
## 단일 μ±…μž„ μ»΄ν¬λ„ŒνŠΈλ‘œ λ¦¬νŒ©ν„°λ§ ν•˜κΈ°
λ§Œμ•½ μ—¬λŸ¬λΆ„μ˜ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ λ§Žμ€ ν™”λ©΄ ν˜Ήμ€ λ§Žμ€ λͺ¨λ‹¬μ„ ν¬ν•¨ν•˜κ³  μžˆλ‹€λ©΄, 예λ₯Ό λ“€μ–΄ ScreenSwitchboard.jsx와 ModalManager.jsx와 같이 λΌμš°νŠΈμ™€ λͺ¨λ‹¬μ„ λ³„λ„μ˜ μ»΄ν¬λ„ŒνŠΈλ‘œ μΆ”μΆœν•¨μœΌλ‘œμ¨ AppShell.jsx μ»΄ν¬λ„ŒνŠΈλ₯Ό μ’€ 더 κΉ”λ”ν•˜κ²Œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.
```tsx
import React, { useState } from "react";
import { BrowserRouter } from "react-router-dom";

import AppHeader from "./AppHeader";
import AppFooter from "./AppFooter";

import ScreenSwitchboard from "./ScreenSwitchboard";
import ModalManager from "./ModalManager";

import "./app-shell.css";

const AppShell = () => {
const [modalOpen, setModal] = useState(false);

const openModal = (event) => {
event.preventDefault();
const {
target: {
dataset: { modal },
},
} = event;
if (modal) setModal(modal);
};

const closeModal = () => {
setModal("");
};

return (
<BrowserRouter>
<div className="app--shell" onClick={openModal}>
<AppHeader />
<ScreenSwitchboard />
<ModalManager closeFn={closeModal} modal={modalOpen} />
<AppFooter />
</div>
</BrowserRouter>
);
};

export default AppShell;
```
## 이벀트 버블링을 μ‚¬μš©ν•˜μ—¬ νŠΉμ • λͺ¨λ‹¬ μ—΄κΈ°
μš°λ¦¬λŠ” #app--shell μš”μ†Œμ—μ„œ λ²„λΈ”λ§λœ 클릭 이벀트λ₯Ό μΊ‘μ²˜ν•©λ‹ˆλ‹€. νŠΉμ • λͺ¨λ‹¬μ„ 열도둝 νŠΈλ¦¬κ±°ν•˜λŠ” 이벀트 ν•Έλ“€λŸ¬ openModal은 우리 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 일뢀 μš”μ†Œ(λ²„νŠΌ, 링크 λ“±)에 μ„€μ •ν•  수 μžˆλŠ” data-modal 속성을 μ°ΎμŠ΅λ‹ˆλ‹€.
μ•„λž˜λŠ” ν΄λ¦­ν–ˆμ„ λ•Œ λͺ¨λ‹¬μ„ 열도둝 νŠΈλ¦¬κ±°ν•˜λŠ” λ²„νŠΌμ΄ μžˆλŠ” ν™”λ©΄ μ»΄ν¬λ„ŒνŠΈμ˜ μ˜ˆμ‹œμž…λ‹ˆλ‹€.
```tsx
import React from "react";

const ScreenOne = ({}) => {
return (
<main className="app--screen screen--one">
<h2>Screen One</h2>

<div style={{ display: "flex", columnGap: "1rem" }}>
<button type="button" data-modal="modal-one">
Open Modal One
</button>
<button type="button" data-modal="modal-two">
Open Modal Two
</button>
<button type="button" data-modal="modal-three">
Open Modal Three
</button>
</div>
</main>
);
};

export default ScreenOne;
```
λ³΄μ‹œλ‹€μ‹œν”Ό, μš°λ¦¬λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 계측 ꡬ쑰 props 내리기λ₯Ό 톡해 ν•¨μˆ˜λ‚˜ 값을 μ „λ‹¬ν•˜κ³  μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λŒ€μ‹ , data-modal 속성과 이벀트 버블링에 μ˜μ‘΄ν•˜μ—¬ νŠΉμ • λͺ¨λ‹¬μ„ μ—¬λŠ” 것을 μ²˜λ¦¬ν•©λ‹ˆλ‹€.
## ModalManager
우리의 <ModalManager /> μ»΄ν¬λ„ŒνŠΈλŠ” 두 가지 propsλ₯Ό μš”κ΅¬ν•©λ‹ˆλ‹€: μ–΄λ–€ λͺ¨λ‹¬μ΄ μ—΄λ €μ•Ό ν•˜λŠ”μ§€λ₯Ό μ„€λͺ…ν•˜λŠ” modal propμœΌλ‘œμ„œμ˜ μƒνƒœ κ°’κ³Ό, μ—΄λ € μžˆλŠ” λͺ¨λ‹¬μ„ 닫도둝 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ 효과적으둜 μ§€μ‹œν•˜λŠ” closeFn propμž…λ‹ˆλ‹€.
μ°Έκ³ : λͺ¨λ‹¬μ€ κ°„λ‹¨ν•œ μ½˜ν…μΈ λ₯Ό 포함할 μˆ˜λ„ 있고, 폼 μ²˜λ¦¬μ™€ 같은 더 λ³΅μž‘ν•œ 경우λ₯Ό λ‹€λ£° μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. μš°λ¦¬λŠ” κ·Έλ“€μ˜ λ‹«νž˜μ„ μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ 클릭 이벀트 버블링에 μ˜μ‘΄ν•˜κ³  싢지 μ•ŠμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ—μ„œ prop을 μ‚¬μš©ν•˜λŠ” 것이 더 κ°„λ‹¨ν•˜κ³  μœ μ—°ν•©λ‹ˆλ‹€.
λ‹€μŒμ€ <ModalManager /> μ»΄ν¬λ„ŒνŠΈμž…λ‹ˆλ‹€.
```tsx
import React from "react";

import ModalOne from "./components/common/modal-one/ModalOne";
import ModalTwo from "./components/common/modal-two/ModalTwo";
import ModalThree from "./components/common/modal-three/ModalThree";

const ModalManager = ({ closeFn = () => null, modal = "" }) => (
<>
<ModalOne closeFn={closeFn} open={modal === "modal-one"} />

<ModalTwo closeFn={closeFn} open={modal === "modal-two"} />

<ModalThree closeFn={closeFn} open={modal === "modal-three"} />
</>
);

export default ModalManager;
```
## React Portal을 μ‚¬μš©ν•˜μ—¬ λͺ¨λ‹¬ λ Œλ”λ§ν•˜κΈ°
ν•œ λ²ˆμ— ν•˜λ‚˜μ˜ Modal만 ν‘œμ‹œν•˜λŠ” 것이 κ°€μž₯ 일반적인 νŒ¨ν„΄μ΄λ―€λ‘œ, μžμ‹ μš”μ†Œλ₯Ό React ν¬ν„Έλ‘œ λ Œλ”λ§ν•  래퍼 μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§Œλ“œλŠ” 것이 νƒ€λ‹Ήν•˜λ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.
λ‹€μŒμ€ src/components/common/modal/Modal.jsx μ»΄ν¬λ„ŒνŠΈμ˜ μ½”λ“œμž…λ‹ˆλ‹€.
```tsx
import React, { useEffect } from "react";
import ReactDOM from "react-dom";

const modalRootEl = document.getElementById("modal-root");

const Modal = ({ children, open = false }) => {
if (!open) return null;

return ReactDOM.createPortal(children, modalRootEl);
};

export default Modal;
```
μš°λ¦¬λŠ” #modal-root μš”μ†Œκ°€ document μ–΄λ”˜κ°€μ—, 특히 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ 마운트된 #app-root μš”μ†Œμ˜ ν˜•μ œ μš”μ†Œλ‘œμ„œ μ‘΄μž¬ν•˜κΈ°λ₯Ό κΈ°λŒ€ν•©λ‹ˆλ‹€.
예λ₯Ό λ“€μ–΄, index.html의 <body />λŠ” λ‹€μŒκ³Ό 같이 보일 수 μžˆμŠ΅λ‹ˆλ‹€:
```html
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
```
λ§ˆμ§€λ§‰μœΌλ‘œ, νŠΉμ • λͺ¨λ‹¬ μ»΄ν¬λ„ŒνŠΈμ˜ μ˜ˆμ‹œμž…λ‹ˆλ‹€.
```tsx
import React from "react";

import Modal from "../modal/Modal";

const ModalOne = ({ closeFn = () => null, open = false }) => {
return (
<Modal open={open}>
<div className="modal--mask">
<div className="modal-window">
<header className="modal--header">
<h1>Modal One</h1>
</header>
<div className="modal--body">
<p>Modal One content will be rendered here.</p>
</div>
<footer className="modal--footer">
<button type="button" onClick={closeFn}>
Close
</button>
</footer>
</div>
</div>
</Modal>
);
};

export default ModalOne;
```
이 κΈ€μ—μ„œλŠ” ꡬ체적인 μ˜ˆμ‹œλ₯Ό λ“€λ©΄μ„œ μƒλŒ€μ μœΌλ‘œ 짧고 κ°„λ‹¨ν•˜κ²Œ λ§Œλ“€κ³ μž λͺ¨λ“  λ‚΄μš©μ„ 닀루지 μ•Šμ•˜μŠ΅λ‹ˆλ‹€. μŠ€νƒ€μΌλ§, μ ‘κ·Όμ„± 그리고 μ•„λ§ˆλ„ λ‹€λ₯Έ μš”μ†Œλ“€μ„ κ³ λ €ν•΄μ•Ό ν•  κ²ƒμž…λ‹ˆλ‹€.
이 κΈ€μ˜ 맨 μœ„μ— κ²Œμ‹œλœ λ§ν¬μ—μ„œ 이 μ†ŒμŠ€ μ½”λ“œλ₯Ό 찾을 수 μžˆμŠ΅λ‹ˆλ‹€.
λŒ“κΈ€λ‘œ 이 글에 λŒ€ν•΄ μ–΄λ–»κ²Œ μƒκ°ν•˜λŠ”μ§€, ν˜Ήμ€ μ—¬λŸ¬λΆ„μ˜ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ λͺ¨λ‹¬μ„ μ–΄λ–»κ²Œ κ΄€λ¦¬ν•˜κ³  μžˆλŠ”μ§€ μ•Œλ €μ£Όμ„Έμš”.

0 comments on commit 1b14da6

Please sign in to comment.