본문 바로가기

React

[React] 간단한 tab(탭) 만들기 (feat. checkbox 추가)

 📌  들어가며

간단한 tab (탭) 입니다.

탭, checkbox(체크박스) 선택 시 해당 내용 노출 기능이 구현되어 있습니다.

구현하면서 알게된 것들을 정리했습니다.

 

📌  구조 작성해보기

1. 제공하려는 데이터 작성

2. 제공된 데이터 title, nickName, communityCategoryCd 값 가져오기

3. 2에서 가져온 값 화면에 뿌려주기

4. 각각의 tab 버튼 클릭 시, 해당 내용 보여주기 (이때, tab 버튼 이름과 communityCategoryCd 를 비교해서 같은 것만 보여주기)

4-1. 선택한 버튼의 현재 상태의 값을 useState 활용해서 저장

4-2. 현재 선택된 버튼과 communityCategoryCd 비교해서 tab 내용 뿌려주기

4-3. 버튼 클릭 시, 활성화되는 css 추가

5. 체크박스 (이번 달) 버튼 추가 

5-1. 현재 시간과 regDttm 값의 년도, 달을 비교해서 해당 달에 등록된 데이터만 필터링

6. 검색 기능 추가 useState 사용

 

📌  데이터 작성

[constants.js]

export const ITEMS = [
    {
      "communityContentSeq": "2683",
      "title": "홈플러스 이번주 전단행사! 5/23~29",
      "content": "미국 프라임등급 척아이롤이랑, 보리먹인돼지 항정살 50프로 할인하네요!",
      "communityCategoryCd": "01",
      "viewCnt": 415,
      "commentCnt": 2,
      "regDttm": "20240526104604",
      "nickName": "팁글모아태산"
    },
    {
      "communityContentSeq": "2686",
      "title": "토스체크 카드 만원 오백원 캐시백",
      "content": "이거 생각보다 쏠쏠하네요\n\n잘 활용하면 좋은 거 같아요\n\n백원이상 백원 캐시백도 있습니다",
      "communityCategoryCd": "02",
      "viewCnt": 270,
      "commentCnt": 0,
      "regDttm": "20240526201131",
      "nickName": "aa"
    },
    {
      "communityContentSeq": "2688",
      "title": "페이북",
      "content": "페이북에서 마이테그하고  구매하심 5000원 할인 됩니다 필요하신 분들 어서 사용하세요~",
      "communityCategoryCd": "03",
      "viewCnt": 220,
      "commentCnt": 0,
      "regDttm": "20240526210425",
      "nickName": "스테이지"
    },
    {
      "communityContentSeq": "2689",
      "title": "도미노피자 포장 50% 할인",
      "content": "28일까지 행사합니다.",
      "communityCategoryCd": "02",
      "viewCnt": 193,
      "commentCnt": 1,
      "regDttm": "20240526220914",
      "nickName": "푸바오"
    },
    {
      "communityContentSeq": "2681",
      "title": "오늘도 CU에서 얼음컵 타기",
      "content": "오늘도 CU에서 얼음컵 타세요",
      "communityCategoryCd": "01",
      "viewCnt": 316,
      "commentCnt": 0,
      "regDttm": "20240526100148",
      "nickName": "정리걸"
    },
    {
      "communityContentSeq": "2677",
      "title": "토스페이 gs샵 14% 할인",
      "content": "토스페이 경유",
      "communityCategoryCd": "01",
      "viewCnt": 360,
      "commentCnt": 0,
      "regDttm": "20240526010328",
      "nickName": "뺘뱌뀨"
    },
    {
      "communityContentSeq": "2684",
      "title": "여름용 주방용품 세일하네요❗️",
      "content": "여름용 주방용품 할인하네요!\nhttps://link.coupang.com/a/bC4Teu",
      "communityCategoryCd": "03",
      "viewCnt": 263,
      "commentCnt": 0,
      "regDttm": "20240526152039",
      "nickName": "특가정보원",
    },
    {
      "communityContentSeq": "2687",
      "title": "GS 샵 띠꿀모으기",
      "content": "GS 샵 티꿀모아서\n적립금처럼 사용해요^^",
      "communityCategoryCd": "02",
      "viewCnt": 171,
      "commentCnt": 0,
      "regDttm": "20240526202009",
      "nickName": "gs샵띠끌모으기"
    },
    {
      "communityContentSeq": "2678",
      "title": "SK하이닉스 수익률 2424% ㄷㄷ.....",
      "content": "엔비디아 열풍에\nSK하이닉스 주가가 20만원대를 넘겼네요...\n\n\n20년도에 자신의 주식 보유 현황을 인증했던 자사 직원이 최근 블라인드에 계좌 보유 현황을 재인증했는데 대박이네요 😳\n\n당시 주 당 7,800원 5,700주였는데\n여전히 5,700주를 보유 중...\n\n와 수익률 2424%.....ㄷㄷ\n\n\n저는...14만원 직전에 털었는데...\n왜 이런 선택을 했을까.....🫠\n물론 매입가는 완전 다르지만 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ\n\n\n그 분의 블라인드 인증 글 캡쳐해서 올려보아요 \n정말 수익률 대박입니다 👍🏻👍🏻👍🏻\n\n\n\n\n",
      "communityCategoryCd": "02",
      "viewCnt": 391,
      "commentCnt": 1,
      "regDttm": "20240426011959",
      "nickName": "부자될꼬동"
    },
    {
      "communityContentSeq": "2682",
      "title": "엔비디아 액면분할 이후",
      "content": ".\n.\n.\n.\n.\n\n오른다 vs 내린다",
      "communityCategoryCd": "03",
      "viewCnt": 187,
      "commentCnt": 6,
      "regDttm": "20240526100341",
      "nickName": "업비트최대주주워렌버핏"
    }
  ]

 

📌  제공 데이터에서  title, nickName, communityCategoryCd 값 가져와서 화면에 뿌려주기

[App.js]

import { useEffect, useState } from 'react';
import {ITEMS} from './constants';

function App() {
  const [tabValue, setTabValue] = useState('all');
  const [list, setList] = useState(ITEMS);

  const onClickTab = (selectedTab) => {
    setTabValue(selectedTab);
  }

  return (
    <>
      <div>
        <input id="month_chk" type="checkbox"/>
        <label for="month_chk">이번 달</label>
      </div>
      <div>
        <div className="tab-list">
          <button type="button" onClick={()=>onClickTab('all')}>전체</button>
          <button type="button" onClick={()=>onClickTab('01')}>01</button>
          <button type="button" onClick={()=>onClickTab('02')}>02</button>
          <button type="button" onClick={()=>onClickTab('03')}>03</button>
        </div>
        <div>
          <ul>
            {list.map((item) => (
              <li key={item.communityContentSeq}>
                <div>title: {item.title}</div>
                <div>nickName: {item.nickName}</div>
                <div>communityCategoryCd: {item.communityCategoryCd}</div>
              </li>
              ))
            }
          </ul>
        </div>
      </div>
    </>
  );
}

export default App;

각각의 버튼에 onClick={()=>onClickTab('01')} 과 같이 넣어준다.

이렇게 넣어놓으면 모든 내용을 다 불러오게 된다.

 

📌  tab 버튼 클릭 시, 해당 내용 보여주기

  • tab 버튼 이름(all, 01, 02, 03) 과 communityCategoryCd 를 비교
  • 선택한 버튼의 현재 상태의 값을 useState 활용해서 저장
  • 현재 선택된 버튼과 communityCategoryCd 비교해서 tab 내용 뿌려주기
  • 버튼 클릭 시, 활성화되는 css 추가

[ App.js ]

import { useEffect, useState } from 'react';
import {ITEMS} from './constants';
import './App.css';

function App() {
  const [tabValue, setTabValue] = useState('all');
  const [list, setList] = useState(ITEMS);

// 버튼 값이 'all'에 해당하는 경우와 나머지에 대해서 비교하여 filter
  useEffect(()=>{
    if (tabValue === 'all') {
      setList(ITEMS);
    } else {
      setList(ITEMS.filter((item) => item.communityCategoryCd === tabValue));
    }
  },[tabValue])

  const onClickTab = (selectedTab) => {
    setTabValue(selectedTab);
  }

  return (
    <>
      <div>
        <input id="month_chk" type="checkbox" />
        <label for="month_chk">이번 달</label>
      </div>
      <div>
        <div className="tab-list">
          <button className={tabValue === "all" ? "active" : ""} type="button" onClick={()=>onClickTab('all')}>전체</button>
          <button className={tabValue === "all" ? "active" : ""} type="button" onClick={()=>onClickTab('01')}>01</button>
          <button className={tabValue === "all" ? "active" : ""} type="button" onClick={()=>onClickTab('02')}>02</button>
          <button className={tabValue === "all" ? "active" : ""} type="button" onClick={()=>onClickTab('03')}>03</button>
        </div>
        <div>
          <ul>
            {list.map((item) => (
              <li key={item.communityContentSeq}>
                <div>title: {item.title}</div>
                <div>nickName: {item.nickName}</div>
                <div>communityCategoryCd: {item.communityCategoryCd}</div>
              </li>
              ))
            }
          </ul>
        </div>
      </div>
    </>
  );
}

export default App;

className={tabValue === "all" ? "active" : ""} 넣어서 버튼 클릭 시 활성화 스타일 적용

 

[ App.css ]

.tab-list button {
  border: 1px solid #c1c1c1;
  background-color: transparent;
  width: 150px;
  height: 35px;
  border-radius: 3px;
}

// 버튼 활성화
.tab-list button.active {
  background-color: #e1664b;
  color: #fff;
}

 

📌  체크박스 (이번 달) 버튼 추가

  • 체크박스 버튼 추가
  • 현재 시간과 regDttm 값의 년도, 달을 비교해서 해당 달에 등록된 데이터만 필터링

[ App.js ]

import { useEffect, useState } from 'react';
import {ITEMS} from './constants';
import './App.css';

function App() {
  const [tabValue, setTabValue] = useState('all');
  const [list, setList] = useState(ITEMS);
  const [showThisMonthOnly, setshowThisMonthOnly] = useState(false);

  useEffect(()=> {
    filterItems();
  },[tabValue, showThisMonthOnly])

  // 해당 탭에서 이번달에 해당하는 filter를 추가한 함수
  const filterItems = () => {
    let filteredItems = ITEMS;
	
    // 버튼 값이 'all'이 아닌 나머지 버튼을 비교 filter
    if (tabValue!== 'all') {
      filteredItems = filteredItems.filter((item) => item.communityCategoryCd === tabValue);
    }

	// 현재 '년/월'값을 가져와서 만들어 놓은 constants.js 데이터에 있는 '년/월' 값을 비교하여 filter
    if (showThisMonthOnly) {
      const currentDate = new Date();
      const currentYear = currentDate.getFullYear();
      const currentMonth = currentDate.getMonth() + 1;

      filteredItems = filteredItems.filter((item) => {
        const regDate = item.regDttm.substring(0, 6);
        const regYear = parseInt(regDate.substring(0, 4));
        const regMonth = parseInt(regDate.substring(4, 6));

        return regYear === currentYear && regMonth === currentMonth;
      });
    }

	// 이 외 나머지 값 (여기서는 'all'에 해당됨)
    setList(filteredItems);
  }

  const onClickTab = (selectedTab) => {
    setTabValue(selectedTab);
  }

// '이번 달' 체크박스 클릭하면 해당 탭에서 '이번 달'에 해당하는 데이터만 보여주기
// prev 현재 값에서 !prev(반대값)으로 변경
// setshowThisMonthOnly((prev) => !prev); 이 방법이 더 정확하지만
// 실무에서는 setshowThisMonthOnly(!showThisMonthOnly); 이거를 더 많이 씀
  const toggleShowThisMonthOnly = () => {
    setshowThisMonthOnly((prev) => !prev);
    // === setshowThisMonthOnly(!showThisMonthOnly);
  }

  return (
    <>
      <div>
        <input id="month_chk" type="checkbox" onChange={toggleShowThisMonthOnly} />
        <label for="month_chk">이번 달</label>
      </div>
      <div>
        <div className="tab-list">
          <button className={tabValue === "all" ? "active" : ""} type="button" onClick={()=>onClickTab('all')}>전체</button>
          <button className={tabValue === "01" ? "active" : ""} type="button" onClick={()=>onClickTab('01')}>01</button>
          <button className={tabValue === "02" ? "active" : ""} type="button" onClick={()=>onClickTab('02')}>02</button>
          <button className={tabValue === "03" ? "active" : ""} type="button" onClick={()=>onClickTab('03')}>03</button>
        </div>
        <div>
          <ul>
            {list.map((item) => (
              <li key={item.communityContentSeq}>
                <div>title: {item.title}</div>
                <div>nickName: {item.nickName}</div>
                <div>communityCategoryCd: {item.communityCategoryCd}</div>
              </li>
              ))
            }
          </ul>
        </div>
      </div>
    </>
  );
}

export default App;

 

📌  검색 기능 추가

  • 검색 input, button 추가
  • useState 사용

[ App.js ]

import { useEffect, useState } from 'react';
import {ITEMS} from './constants';
import './App.css';

function App() {
  const [tabValue, setTabValue] = useState('all');
  const [list, setList] = useState(ITEMS);
  const [showThisMonthOnly, setshowThisMonthOnly] = useState(false);
  // 검색 useState 추가
  const [search, setSearch] = useState('');

  useEffect(()=>{
    // console.log(tabValue);
    // if (tabValue === 'all') {
    //   setList(ITEMS);
    // } else {
    //   setList(ITEMS.filter((item) => item.communityCategoryCd === tabValue));
    // }
    filterItems();
  },[tabValue, showThisMonthOnly])

  const filterItems = () => {
    let filteredItems = ITEMS;

    if (tabValue!== 'all') {
      filteredItems = filteredItems.filter((item) => item.communityCategoryCd === tabValue);
    }

    if (showThisMonthOnly) {
      const currentDate = new Date();
      const currentYear = currentDate.getFullYear();
      const currentMonth = currentDate.getMonth() + 1;

      filteredItems = filteredItems.filter((item) => {
        const regDate = item.regDttm.substring(0, 6);
        const regYear = parseInt(regDate.substring(0, 4));
        const regMonth = parseInt(regDate.substring(4, 6));

        return regYear === currentYear && regMonth === currentMonth;
      });
    }
    
    // tab에서 filter가 된 이후에, 검색 기능 로직 타야하기 때문에 추가
    // 검색어가 없을 때와 있을 때
    if (search !== ""){
      filterTitle(filteredItems)
      // filteredItems = filteredItems.filter((item) => item.title.replace(" ", "").toLocaleLowerCase().includes(search.toLocaleLowerCase().replace(" ", "")))
    }else{
      setList(filteredItems);
    }
  }

  const onClickTab = (selectedTab) => {
    // console.log(e.target.value);
    setTabValue(selectedTab);
  }

  const toggleShowThisMonthOnly = () => {
    setshowThisMonthOnly((prev) => !prev);
    // === setshowThisMonthOnly(!showThisMonthOnly);
  }

  const handleSearch = (e) => {
    setSearch(e.target.value);
    // console.log(search);
  }

  // 검색 기능 추가
  // 검새겅가 있을 때와 없을때로 나누고, 검색어가 없는 상태에서 검색 버튼 클릭 시,
  // 해당 탭을 클릭하면 해당 탭에 있는 내용이 보여야 한다.
  // 중복되는 부분이 있어 보이지만, 탭 안엔서 이루어지는 기능이기 때문에 중복되는 부분이 있음
  const filterTitle = (items) => {
    if (search) {
      setList(items.filter((item) => {
        return (
          item.title.toLocaleLowerCase().includes(search.toLocaleLowerCase()))
      }))
    } else {
      if (tabValue !== 'all') {
        setList(ITEMS.filter((item) => item.communityCategoryCd === tabValue));
      } else {
        setList(ITEMS);
      }
    }
  }

  return (
    <>
      <div>
        <div>
          <input type="text" value={search} onChange={handleSearch} />
          <button type="button" onClick={()=>{filterTitle(list)}}>검색</button>
        </div>
      </div>
      <div>
        <input id="month_chk" type="checkbox" onChange={toggleShowThisMonthOnly} />
        <label for="month_chk">이번 달</label>
      </div>
      <div>
        <div className="tab-list">
          <button className={tabValue === "all" ? "active" : ""} type="button" onClick={()=>onClickTab('all')}>전체</button>
          <button className={tabValue === "01" ? "active" : ""} type="button" onClick={()=>onClickTab('01')}>01</button>
          <button className={tabValue === "02" ? "active" : ""} type="button" onClick={()=>onClickTab('02')}>02</button>
          <button className={tabValue === "03" ? "active" : ""} type="button" onClick={()=>onClickTab('03')}>03</button>
        </div>
        <div>
          <ul>
            {list.map((item) => (
              <li key={item.communityContentSeq}>
                <div>title: {item.title}</div>
                <div>nickName: {item.nickName}</div>
                <div>communityCategoryCd: {item.communityCategoryCd}</div>
                <div>날짜: {item.regDttm}</div>
              </li>
              ))
            }
          </ul>
        </div>
      </div>
    </>
  );
}

export default App;