[React] useState 로 좋아요 버튼 상태 관리하기
안녕하세요 오늘은 지난 시간에 만들었던 슬라이드 UI 카드에 좋아요 버튼을 구현하고자 합니다.
지난 게시글은 아래 링크에서 확인하실 수 있습니다.
[React] 슬라이드 카드 UI 만들기
안녕하세요. 오늘은 리액트를 사용해 슬라이드 카드 UI를 만들어보겠습니다. 슬라이드 카드는 이미지, 텍스트 등을 한 화면에서 넘기며 보여줄 수 있는 방식으로, 포트폴리오, 프로젝트 소개, 리
stickode.tistory.com
1. 먼저 좋아요 버튼을 "컴포넌트" 디렉토리에 만들어줍니다.
components 폴더가 없다면 만들어줍니다
경로: src/components/LikeButton.js
import { useState } from "react";
function LikeButton() {
const [liked, setLiked] = useState(false);
const toggleLike = () => setLiked(!liked);
return (
<button
onClick={toggleLike}
style={{
position: "absolute",
top: "20px",
right: "20px",
fontSize: "24px",
background: "none",
border: "none",
cursor: "pointer",
color: liked ? "red" : "white",
zIndex: 5,
transition: "color 0.3s ease",
}}
>
{liked ? "❤️" : "🤍"}
</button>
);
}
export default LikeButton;
2. App.js 에서 방금 만든 좋아요 버튼을 import 해준 뒤 적당한 위치에 넣어줍니다
import
import LikeButton from "./components/LikeButton";
<LikeButton /> 태그 삽입
<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={() => toggleLike(index)}
/>
<h2>{card.title}</h2>
<p>{card.description}</p>
</div>
</div>
</div>
))}
</Slider>
</div>
</div>
슬라이드 카드 내부에 좋아요 하트가 위치하도록 했습니다.
3. App. js 에서 카드별 상태 만들기
위에서 간단하게 좋아요 버튼 (하트) 를 만들어보았다면, 이제 좋아요 상태를 관리해보도록 하겠습니다.
먼저, App.js에서 카드별 상태를 만들어줍니다. (function App 내부에 아래 내용을 추가해줍니다)
function App() {
const [likes, setLikes] = useState(Array(cardData.length).fill(false));
const toggleLike = (index) => {
const updatedLikes = [...likes];
updatedLikes[index] = !updatedLikes[index];
setLikes(updatedLikes);
};
아까 작성했던 <LikeButton /> 태그에 좋아요 상태를 전달해줍니다
<LikeButton
liked={likes[index]}
onToggle={() => toggleLike(index)}
/>
단순히 버튼을 누름으로써 하트 색상만 변경되는 것이 아니라, 이를 나의 "상태"로 보고 좋아요 여부 정보를 전달합니다.
LikeButton 코드도 수정해줍니다.
전달 받은 좋아요 상태, 토글 상태를 props 로 받습니다.
function LikeButton({ liked, onToggle }) {
return (
<button
onClick={onToggle}
style={{
position: "absolute",
top: "20px",
right: "20px",
fontSize: "24px",
background: "none",
border: "none",
cursor: "pointer",
color: liked ? "red" : "white",
zIndex: 5,
transition: "color 0.3s ease",
}}
>
{liked ? "❤️" : "🤍"}
</button>
);
}
export default LikeButton;
좋아요 상태관리는 App.js 에서 전부 관리하고 LikeButton 은 전달 받은 대로 ui 를 생성하는 역할을 하기 때문에 상태 관리를 위한 useState 를 사용할 필요가 없습니다.
App. js 로부터 liked, onToggle 를 전달 받아 ui 로 띄워줍니다.
최종적인 전체 코드 입니다
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";
// 슬라이드 카드 데이터
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);
};
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={() => toggleLike(index)}
/>
<h2>{card.title}</h2>
<p>{card.description}</p>
</div>
</div>
</div>
))}
</Slider>
</div>
</div>
}
/>
<Route path="/about" element={<About />} />
<Route path="/experience" element={<Experience />} />
<Route path="/projects" element={<Projects />} />
</Routes>
</Router>
);
}
export default App;
2. LikeButton.js
function LikeButton({ liked, onToggle }) {
return (
<button
onClick={onToggle}
style={{
position: "absolute",
top: "20px",
right: "20px",
fontSize: "24px",
background: "none",
border: "none",
cursor: "pointer",
color: liked ? "red" : "white",
zIndex: 5,
transition: "color 0.3s ease",
}}
>
{liked ? "❤️" : "🤍"}
</button>
);
}
export default LikeButton;
다음 시간에는 로컬 스토리지를 활용해 좋아요 상태를 저장해보도록 하겠습니다.
감사합니다
시연영상입니다.