-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from Wannabe-Woowa-Article/lurgi
- Loading branch information
Showing
1 changed file
with
278 additions
and
0 deletions.
There are no files selected for viewing
278 changes: 278 additions & 0 deletions
278
June/article/How-to-handle-multiple-modals-in-a-React-application.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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">© 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; | ||
``` | ||
μ΄ κΈμμλ ꡬ체μ μΈ μμλ₯Ό λ€λ©΄μ μλμ μΌλ‘ μ§§κ³ κ°λ¨νκ² λ§λ€κ³ μ λͺ¨λ λ΄μ©μ λ€λ£¨μ§ μμμ΅λλ€. μ€νμΌλ§, μ κ·Όμ± κ·Έλ¦¬κ³ μλ§λ λ€λ₯Έ μμλ€μ κ³ λ €ν΄μΌ ν κ²μ λλ€. | ||
μ΄ κΈμ 맨 μμ κ²μλ λ§ν¬μμ μ΄ μμ€ μ½λλ₯Ό μ°Ύμ μ μμ΅λλ€. | ||
λκΈλ‘ μ΄ κΈμ λν΄ μ΄λ»κ² μκ°νλμ§, νΉμ μ¬λ¬λΆμ μ ν리μΌμ΄μ μμ λͺ¨λ¬μ μ΄λ»κ² κ΄λ¦¬νκ³ μλμ§ μλ €μ£ΌμΈμ. |