728x90
안녕하세요 오늘은 좋아요 버튼 누를때 확인 모달을 띄워보도록 하겠습니다.
좋아요 버튼 구현에 대한 게시글은 아래 링크에서 확인하실 수 있습니다
[React] useState 로 좋아요 버튼 상태 관리하기
안녕하세요 오늘은 지난 시간에 만들었던 슬라이드 UI 카드에 좋아요 버튼을 구현하고자 합니다. 지난 게시글은 아래 링크에서 확인하실 수 있습니다. [React] 슬라이드 카드 UI 만들기안녕하세요.
stickode.tistory.com
1. 먼저
src/components/ 디렉토리에 ConfirmModal.js 파일을 만들어줍니다.
모달 창 ui 코드를 작성합니다
ConfirmModal.js
// src/components/ConfirmModal.js
function ConfirmModal({ isOpen, onConfirm, onCancel }) {
if (!isOpen) return null;
return (
<div style={modalStyle.backdrop}>
<div style={modalStyle.modal}>
<p style={{ marginBottom: "20px" }}>정말 좋아요 하시겠습니까?</p>
<button onClick={onConfirm} style={modalStyle.button}>확인</button>
<button onClick={onCancel} style={{ ...modalStyle.button, marginLeft: "10px" }}>취소</button>
</div>
</div>
);
}
const modalStyle = {
backdrop: {
position: "fixed",
top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: "rgba(0, 0, 0, 0.5)",
display: "flex", alignItems: "center", justifyContent: "center",
zIndex: 1000,
},
modal: {
backgroundColor: "white",
padding: "20px 30px",
borderRadius: "12px",
textAlign: "center",
boxShadow: "0 0 15px rgba(0,0,0,0.3)",
},
button: {
padding: "8px 16px",
backgroundColor: "#007BFF",
border: "none",
color: "white",
borderRadius: "5px",
cursor: "pointer",
},
};
export default ConfirmModal;
- 모달은 props로 isOpen, onConfirm, onCancel 상태를 받아서 제어됩니다.
2. ui 파일을 import 해줍니다.
App.js
import ConfirmModal from "./components/ConfirmModal.js";
3. 최상위 컴포넌트인 function App() 컴포넌트 내부에 좋아요 클릭시 모달 창이 뜨는 기능을 추가해줍니다.
App.js
// 기존 toggleLike → 아래처럼 바꿈
const handleLikeClick = (index) => {
setPendingLikeIndex(index);
setModalOpen(true);
};
const confirmLike = () => {
if (pendingLikeIndex !== null) {
const newLikes = [...likes];
newLikes[pendingLikeIndex] = !newLikes[pendingLikeIndex];
setLikes(newLikes);
setPendingLikeIndex(null);
setModalOpen(false);
}
};
const cancelLike = () => {
setPendingLikeIndex(null);
setModalOpen(false);
};
전체 코드 입니다
전체코드(1) - App.js
import "./App.css";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import Slider from "react-slick";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import About from "./pages/about.js";
import Experience from "./pages/experiences.js";
import Projects from "./pages/projects.js";
import LikeButton from "./components/LikeButton.js";
import { useState } from "react";
import ConfirmModal from "./components/ConfirmModal.js";
// 슬라이드 카드 데이터
const cardData = [
{
title: "1. Wall Sina",
description:
"The innermost wall, where the royal family and nobles reside.",
image: "https://source.unsplash.com/random/800x400?city",
},
{
title: "2. Wall Rose",
description: "The middle wall, home to many of the general populace.",
image: "https://source.unsplash.com/random/800x400?nature",
},
{
title: "3. Wall Maria",
description:
"The outermost and largest wall, the first to fall during the Titans' initial attack.",
image: "https://source.unsplash.com/random/800x400?space",
},
];
// 커스텀 화살표
function PrevArrow(props) {
return (
<button
onClick={props.onClick}
style={{
position: "absolute",
left: "-60px",
top: "50%",
transform: "translateY(-50%)",
zIndex: 10,
padding: "12px 18px",
fontSize: "20px",
borderRadius: "50%",
border: "none",
backgroundColor: "rgba(0, 0, 0, 0.6)",
color: "white",
cursor: "pointer",
}}
>
←
</button>
);
}
function NextArrow(props) {
return (
<button
onClick={props.onClick}
style={{
position: "absolute",
right: "-60px",
top: "50%",
transform: "translateY(-50%)",
zIndex: 10,
padding: "12px 18px",
fontSize: "20px",
borderRadius: "50%",
border: "none",
backgroundColor: "rgba(0, 0, 0, 0.6)",
color: "white",
cursor: "pointer",
}}
>
→
</button>
);
}
// App 컴포넌트
function App() {
const settings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
nextArrow: <NextArrow />,
prevArrow: <PrevArrow />,
};
// 메뉴바
const headerStyle = {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "20px 40px",
backgroundColor: "#0d1b2a",
color: "white",
};
// 화살표
const navStyle = {
display: "flex",
gap: "20px",
alignItems: "center",
};
// 좋아욧
const [likes, setLikes] = useState(Array(cardData.length).fill(false));
// const toggleLike = (index) => {
// const updatedLikes = [...likes];
// updatedLikes[index] = !updatedLikes[index];
// setLikes(updatedLikes);
// };
// 좋아요 모달
const [modalOpen, setModalOpen] = useState(false);
const [pendingLikeIndex, setPendingLikeIndex] = useState(null);
// 기존 toggleLike → 아래처럼 바꿈
const handleLikeClick = (index) => {
setPendingLikeIndex(index);
setModalOpen(true);
};
const confirmLike = () => {
if (pendingLikeIndex !== null) {
const newLikes = [...likes];
newLikes[pendingLikeIndex] = !newLikes[pendingLikeIndex];
setLikes(newLikes);
setPendingLikeIndex(null);
setModalOpen(false);
}
};
const cancelLike = () => {
setPendingLikeIndex(null);
setModalOpen(false);
};
return (
<Router>
<header style={headerStyle}>
<div style={{ fontWeight: "bold", fontSize: "20px" }}>
<Link to="/" style={{ color: "white", textDecoration: "none" }}>
Hiii It's mee !
</Link>
</div>
<nav style={navStyle}>
<Link to="/" style={{ color: "white", textDecoration: "none" }}>
Home
</Link>
<Link to="/about" style={{ color: "white", textDecoration: "none" }}>
About
</Link>
<Link
to="/experience"
style={{ color: "white", textDecoration: "none" }}
>
Experience
</Link>
<Link
to="/projects"
style={{ color: "white", textDecoration: "none" }}
>
Projects
</Link>
</nav>
</header>
<Routes>
<Route
path="/"
element={
<div style={{ padding: "50px", textAlign: "center" }}>
<div className="slider-wrapper">
<Slider {...settings}>
{cardData.map((card, index) => (
<div key={index} className="slide">
<div
className="slide-background"
style={{
backgroundImage: `url(${card.image})`,
height: "400px",
backgroundSize: "cover",
backgroundPosition: "center",
position: "relative",
borderRadius: "20px",
overflow: "hidden",
}}
>
<div
className="text-box"
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
backgroundColor: "rgba(0,0,0,0.6)",
padding: "30px",
width: "80%",
maxWidth: "600px",
minWidth: "250px",
borderRadius: "20px",
textAlign: "center",
color: "white",
}}
>
<LikeButton
liked={likes[index]}
onToggle={() => handleLikeClick(index)}
/>
<h2>{card.title}</h2>
<p>{card.description}</p>
</div>
</div>
</div>
))}
</Slider>
<ConfirmModal
isOpen={modalOpen}
onConfirm={confirmLike}
onCancel={cancelLike}
/>
</div>
</div>
}
/>
<Route path="/about" element={<About />} />
<Route path="/experience" element={<Experience />} />
<Route path="/projects" element={<Projects />} />
</Routes>
</Router>
);
}
export default App;
전체코드(2) - ConfirmModals.js
// src/components/ConfirmModal.js
function ConfirmModal({ isOpen, onConfirm, onCancel }) {
if (!isOpen) return null;
return (
<div style={modalStyle.backdrop}>
<div style={modalStyle.modal}>
<p style={{ marginBottom: "20px" }}>정말 좋아요 하시겠습니까?</p>
<button onClick={onConfirm} style={modalStyle.button}>확인</button>
<button onClick={onCancel} style={{ ...modalStyle.button, marginLeft: "10px" }}>취소</button>
</div>
</div>
);
}
const modalStyle = {
backdrop: {
position: "fixed",
top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: "rgba(0, 0, 0, 0.5)",
display: "flex", alignItems: "center", justifyContent: "center",
zIndex: 1000,
},
modal: {
backgroundColor: "white",
padding: "20px 30px",
borderRadius: "12px",
textAlign: "center",
boxShadow: "0 0 15px rgba(0,0,0,0.3)",
},
button: {
padding: "8px 16px",
backgroundColor: "#007BFF",
border: "none",
color: "white",
borderRadius: "5px",
cursor: "pointer",
},
};
export default ConfirmModal;
시연 영상입니다.
'React' 카테고리의 다른 글
[React] useReducer 사용하여 간단한 계산기 만들기 (0) | 2025.06.16 |
---|---|
[React] Todo-List 만들기 (2) | 2025.06.09 |
[React] useState 로 좋아요 버튼 상태 관리하기 (0) | 2025.05.28 |
[React] 메뉴바로 페이지 전환하기 (0) | 2025.05.21 |
[React] 슬라이드 카드 UI 만들기 (0) | 2025.05.15 |