TIL39 | React | .map 메서드 사용시 요소 한 개만 선택하기

미연·2021년 10월 25일
2

WANTED-clone

목록 보기
3/4
post-thumbnail
post-custom-banner

함수형 컴포넌트로 .map 메서드 사용시 요소 한 개만 선택하기.

  • 상수 데이터 기반의 포스팅인 점 참고해 주세요!

1. state 초기 설정

const [tagList] = useState(TAG_LIST);
const [, setChoiceTagID] = useState(1);
const [clickValue, setClickValue] = useState(false);
const [filterTag, setFilterTag] = useState([]);

const TAG_LIST = [
  { id: 1, tag_category: '업계연봉수준', color: 'rgb(246, 246, 246)' },
  { id: 2, tag_category: '투자', color: 'rgb(240, 248, 248)' },
  { id: 3, tag_category: '인원성장률', color: 'rgb(238, 237, 244)' },
  { id: 4, tag_category: '퇴사율', color: 'rgb(238, 245, 246)' },
  { id: 5, tag_category: '인원수', color: 'rgb(236, 249, 247)' },
  { id: 6, tag_category: '업력', color: 'rgb(239, 251, 243)' },
  { id: 7, tag_category: '보상', color: 'rgb(233, 231, 238)' },
  { id: 8, tag_category: '기업문화', color: 'rgb(240, 249, 245)' },
  { id: 9, tag_category: '가족', color: 'rgb(233, 244, 251)' },
  { id: 10, tag_category: '출퇴근', color: 'rgb(236, 241, 241)' },
];
  • 백엔드 API 데이터를 받아오기 전에, 상수 데이터를 미리 만들고 state인 tagList에 담아주었다.
  • 특정 데이터의 id를 기준으로 비교를 해 줄 것이다. 여기서 id는 인덱스 값이랑 맞춰주었다.
  • choiceTagID : 특정한 데이터의 ID를 담아줄 state
  • clickValue : class가 꺼졌는지(false) 켜졌는지(true) 판별해줄 state
  • filterTag : 선택한 값만을 필터링할 때 담아줄 배열 state

2. onClick 메서드

  const clickTagBtn = id => {
    setChoiceTagID(id);
    setClickValue(!clickValue);
    tagList[id].isChecked = !clickValue;
  };
  1. 클릭한 요소의 id를 인자로 받아온다.
  2. 인자로 받은 idchoiceTagID state에 담는다.
  3. 클릭했을 때 true <-> false를 반복해주는 setClickValue(!clickValue);
  4. id는 일부러 인덱스와 같게 해주었다. 이때 tagList의 인덱스로 접근하여, 키 isChecked의 값을 true 혹은 false으로 바꿔준다.

3. map 메서드 사용

 <ul className="tagPickWrap">
            {tagList.map(tag => (
              <TagData key={tag.id} tagData={tag} clickTagBtn={clickTagBtn} />
            ))}
</ul>
  • 반복되는 <li> 요소를 컴포넌트화하여 map으로 돌렸다.
// TagData.js
function TagData({ tagData, clickTagBtn }) {
  return (
    <li className="tags">
      <button
        className={`${tagData.isChecked ? 'tagBtn' : 'tagBtnOFF'}`}
        style={{ backgroundColor: tagData.color }}
        onClick={() => clickTagBtn(tagData.id)}
      >
        {tagData.tag_category}
      </button>
    </li>
  );
}
  • onClick 메서드에서 바꾼 isChecked(true/false) 기준으로 클래스명 전환이 이루어진다.
  • 이때 tagBtn는 선택되었다는 의미로 파란색 테두리를 주었다.
  • <button>에 onClick 메서드를 달아 부모 컴포넌트의 함수를 실행하도록 한다. 이때 인자에 id를 넣고 같이 전달한다.

4. 상수 데이터에서 생기는 일

  • 만약 위의 코드에서 퇴사율인 태그 요소를 클릭했으면 아래처럼 변경된다.
// 변경 전
{
    id: 3,
    tag_category: '퇴사율',
    color: 'rgb(238, 245, 246)',
    isChecked: false,
}
// 변경 후
{
    id: 3,
    tag_category: '퇴사율',
    color: 'rgb(238, 245, 246)',
    isChecked: true,
}  
  • 여기까지 선택된 태그 요소 하나가 파란색 테두리로 변경되는 작업이다.

5. 선택된 요소만 나타나게 하는 메서드

  • 맨처음에 filterTag를 빈 배열 state로 선언해 주었었다.
  • 이제부터는 filterTag에 선택된 태그만을 담을 것이고, filterTag에 선택된 태그들이 담겼으면 filterTag.map을 쓸 것이다.
 useEffect(() => {
    setFilterTag(tagList.filter(tag => tag.isChecked === true));
  }, [clickValue, tagList]);

💡 맨 처음에는 디펜덴시 없이, useEffect(()=>{}) 의 형태만을 써주었었다. 그랬더니 개발자도구에 보이는 건 백몇개씩 쌓이는 무한루프 error... 그래서 디펜덴시에 useEffect가 언제 쓰여서 언제 렌더링이 될 건지, 시점을 정해주어야 했다.

  • 배열 안의 객체들(tag) 중 isChecked 속성이 true인 것만 필터링하여 filterTag state에 담아주었다.
  • 이때 디펜덴시(=언제 렌더가 될 건지)는 clickValuetagList가 변경될 때이다.
    • clickValue : 태그 요소가 클릭될 때.
    • tagList : 배열 중 true <-> false가 계속 바뀔 때.

6. 선택된 요소만 나타나는 map

<ul className="choiceTagList">
            {filterTag.map(tag => (
              <li className="choiceTags" key={tag.id}>
                <button className="choiceTagBtn">{tag.tag_category}</button>
              </li>
            ))}
</ul>
  • 5번에서 필터링된 배열을 map 메서드로 돌려 주었다.
  • 필터링된 배열은 true <-> false가 바뀔때마다 변경된다.

전체 코드

BodyTags.js

function BodyTags() {
  const [tagList] = useState(TAG_LIST);
  const [, setChoiceTagID] = useState(1);
  const [clickValue, setClickValue] = useState(false);
  const [filterTag, setFilterTag] = useState([]);

  const clickTagBtn = id => {
    setChoiceTagID(id);
    setClickValue(!clickValue);
    tagList[id].isChecked = !clickValue;
  };

  useEffect(() => {
    setFilterTag(tagList.filter(tag => tag.isChecked === true));
  }, [clickValue, tagList]);

  return (
    <>
      <div className="BodyTags">
        <p className="TagText">
          기업의 특별한 복지, 혜택 등 태그를 선택하여
          <br />
          나에게 꼭 맞는 포지션을 찾아보세요!
        </p>
        <div className="TagPick">
          <p className="tagPickText">카테고리 선택</p>
          <ul className="tagPickWrap">
            {tagList.map(tag => (
              <TagData key={tag.id} tagData={tag} clickTagBtn={clickTagBtn} />
            ))}
          </ul>
          <p className="tagPickText">선택된 태그</p>
          <ul className="choiceTagList">
            {filterTag.map(tag => (
              <li className="choiceTags" key={tag.id}>
                <button className="choiceTagBtn">{tag.tag_category}</button>
              </li>
            ))}
          </ul>
          <p className="guideText">
            해당 태그를 모두 만족하는 포지션이 노출됩니다.
          </p>
        </div>
      </div>
      <Footer />
    </>
  );
}

TagData.js

function TagData({ tagData, clickTagBtn }) {
  return (
    <li className="tags">
      <button
        className={`${tagData.isChecked ? 'tagBtn' : 'tagBtnOFF'}`}
        style={{ backgroundColor: tagData.color }}
        onClick={() => clickTagBtn(tagData.id)}
      >
        {tagData.tag_category}
      </button>
    </li>
  );
}

다른 modal에도 똑같이 적용해 보았다.

profile
FE Developer
post-custom-banner