React-Hooks를 활용하여 버튼 클릭시 데이터를 필터링 해주는 웹 페이지를 구현하려고합니다.
작은 미니 프로젝트로 그래픽카드 정보를 담는 페이지입니다.
npx create-react-app filter-app
으로 리액트 앱을 생성해 줍니다.
생성된 React App에서 불필요한 파일들을 정리 해 줍니다.
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import Display from "./Display";
ReactDOM.render(
<React.StrictMode>
<Display />
</React.StrictMode>,
document.getElementById("root")
);
body {
box-sizing: border-box;
margin: 0;
width: 100%;
height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
import { useState } from "react";
import Card from "./components/Card";
import "./display.css";
import items from "./dummy.json";
export default function Display() {
const categories = ["All", ...new Set(items.map((item) => item.company))];
console.log(categories);
const [activeCat, setActiveCat] = useState(categories);
const [data, setData] = useState(items);
const activeCategory = (btn) => {
if (btn === "All") {
setData(items);
return;
}
const filteredData = items.filter((item) => item.company === btn);
setData(filteredData);
};
return (
<main>
<header>
<h1>그래픽 카드</h1>
</header>
<section>
<article className="categories">
{activeCat.map((cate) => {
return (
<button
className="cat_btn hover"
onClick={() => activeCategory(cate)}
>
{cate}
</button>
);
})}
</article>
<article className="card_list">
{data.map((g, i) => {
return (
<div className="card_container">
<Card card={g} key={i} />
</div>
);
})}
</article>
</section>
<footer></footer>
</main>
);
}
main {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main > section {
display: flex;
flex-direction: column;
}
.categories {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.cat_btn {
width: 170px;
height: 50px;
margin: 0 20px;
color: white;
cursor: pointer;
font-family: "Noto Sans", sans-serif;
font-size: 2rem;
font-weight: bold;
letter-spacing: 1px;
box-shadow: -1px 3px 3px 0 rgba(80, 80, 80, 0.698);
border: none;
border-radius: 5px;
}
.hover {
background: rgba(54, 54, 54, 0.424);
transition: all 0.3s ease;
}
.hover:hover {
width: 180px;
background: rgba(11, 195, 11, 0.74);
box-shadow: -1px 6px 10px 0 rgba(54, 54, 54, 0.424);
}
.hover:visited {
width: 180px;
background: rgba(11, 195, 11, 0.74);
box-shadow: -1px 6px 10px 0 rgba(54, 54, 54, 0.424);
}
.card_list {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
import { specialChars } from "@testing-library/user-event";
import "./card.css";
export default function Card({ card }) {
// console.log(card);
const { model, kind, company, image, spec } = card;
console.log(spec.architecture);
return (
<>
<header className="card_header">
<h1>{model}</h1>
</header>
<figure className="image_box">
<img src={image} />
</figure>
<div className="card_info">
<span>제조사: {company}</span>
<span>종류: {kind}</span>
<span>모델명: {model}</span>
<span>아키텍쳐: {spec.architecture}</span>
<span>메모리: {spec.vram}</span>
</div>
</>
);
}
.card_container {
display: flex;
flex-direction: column;
align-items: center;
/* border: 1px black solid; */
width: 20%;
margin: 3% 1%;
padding: 1%;
box-shadow: -1px 6px 10px 0 rgba(80, 80, 80, 0.473);
}
.card_header {
font-size: 2em;
}
.image_box {
width: 100%;
}
.image_box img {
width: 100%;
height: 100%;
object-fit: contain;
}
.card_info {
width: 100%;
padding-left: 20%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
font-size: 1.1em;
}
위에서처럼 단순히 map을 사용하여 Button을 나열했다면 이번 방법은 UseEffect를 활용한 다른 방식으로 작성됬습니다.
이번에는 버튼을 눌렀을때, 어느 버튼을 눌렀는지 알기위해 버튼효과를 유지시키기 위한 방법도 들어가있습니다.
const [activeCat, setActiveCat] = useState("All");
const [data, setData] = useState(items);
// const activeCategory = (btn) => {
// if (btn === "All") {
// setData(items);
// return;
// }
// const filteredData = items.filter((item) => item.company === btn);
// setData(filteredData);
// };
useEffect(() => {
activeCat === "All"
? setData(items)
: setData(items.filter((vga) => vga.company === activeCat));
}, [activeCat]);
return (
<main>
<header>
<h1>그래픽 카드</h1>
</header>
<section>
<article className="categories">
{/* {activeCat.map((cate) => {
return <button className="cat_btn hover">{cate}</button>;
})} */}
{/* {activeCat.map((cat) => {
<Card name={cat} />;
})} */}
<Catbtn
name="All"
catActive={activeCat === "All" ? true : false}
handleSetCat={setActiveCat}
/>
<Catbtn
name="Nvidia"
catActive={activeCat === "Nvidia" ? true : false}
handleSetCat={setActiveCat}
/>
<Catbtn
name="AMD"
catActive={activeCat === "AMD" ? true : false}
handleSetCat={setActiveCat}
/>
</article>
activeCat 의 기본상태는 "All"입니다. 웹페이지 렌더링시 전체 제품을 보여줍니다.
아래의 useEffect를 사용해줘 각 버튼을 누를때마다 activeCat의 상태가 변경됩니다.
: setData(items.filter((vga) => vga.company === activeCat));
이부분은 Json형태로 작성된 더미파일에서 객체의 값이 선택한 버튼의 이름과 같다면 해당 제품을 제외한 다른 제품들은 필터링 됩니다.
main {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main > section {
display: flex;
flex-direction: column;
}
.categories {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.card_list {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
import "./catbtn.css";
export default function Catbtn({ name, catActive, handleSetCat }) {
return (
<button
className={`cat_btn hover ${catActive ? "active_btn" : null}`}
onClick={() => handleSetCat(name)}
>
{name}
</button>
);
}
Catbtn은 상위 컴포넌트 Display에서 전달받은 props를 활용해줍니다.
<button>
태그에서 className의 속성을 보시면 선택된 버튼에 따라 CSS 스타일링 효과를 줄 수 있습니다.
.cat_btn {
width: 170px;
height: 50px;
margin: 0 20px;
color: white;
cursor: pointer;
font-family: "Noto Sans", sans-serif;
font-size: 2rem;
font-weight: bold;
letter-spacing: 1px;
box-shadow: -1px 3px 3px 0 rgba(80, 80, 80, 0.698);
border: none;
border-radius: 5px;
}
.hover {
background: rgba(54, 54, 54, 0.424);
transition: all 0.3s ease;
}
.hover:hover {
width: 180px;
background: rgba(11, 195, 11, 0.74);
box-shadow: -1px 6px 10px 0 rgba(54, 54, 54, 0.424);
}
.active_btn:active,
.active_btn:focus {
width: 180px;
background: rgba(11, 195, 11, 0.74);
box-shadow: -1px 6px 10px 0 rgba(54, 54, 54, 0.424);
}