본문 바로가기
React

[React] 좋아요 버튼 클릭 시 확인 모달 띄우기

by teamnova 2025. 6. 4.
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;

 

시연 영상입니다.