[TIL] 220128

먼지·2022년 10월 28일
0

TIL

목록 보기
39/57
post-thumbnail

태그로 검색하기 구현

tagList state에서 태그가 3개 체크되게 하고, 체크된 값에 따라서 list?tagList=VEGAN,ZERO_WASTE 이런 식으로 API URL 요청을 해야 한다.

const tagsData = [
  { checked: false, value: 'VEGAN', name: '비건' },
  { checked: false, value: 'VEGANRECIPE', name: '비건 레시피' },
  ...
];
  
export default function ChallengeList() {
  const urlSearchParams = new URLSearchParams(location.search);
  const tagListParam = urlSearchParams.get('tagList') || '';
  const [tagList, setTagList] = useState(tagsData);
  ...
  const { data } = useQuery(
    [
      `${categoryParam}Challenge/${orderParam}/${pageParam}/${queryParam}/${searchTypeParam}`,
      categoryParam,
      queryParam,
      orderParam,
      pageParam,
      searchTypeParam,
      tagListParam,
    ],
    () =>
      getChallengeList({
        query: queryParam,
        page: Number(pageParam),
        order: orderParam,
        category: categoryParam,
        searchType: searchTypeParam,
        tagList: tagListParam,
      })
  );

  // 태그들을 변경하는 부분
  const handleChageTagList = (value: ChallengeTag) => {
    setTagList((prev) => {
      const checkable = prev.filter((p) => p.checked).length <= 2;
      const newTagList = prev.map((p) => {
        if (!p.checked && !checkable) return p;
        return p.value === value ? { ...p, checked: !p.checked } : p;
      });
      const tagListStr = newTagList
        .filter((tag) => tag.checked)
        .map((cTag) => cTag.value)
        .join(',');
      changeSearchParams([['tagList', tagListStr]])();
      return newTagList;
    });
  };
  
  return ();
}

처음 시도한 방법이 handleChageTagList 함수 부분에서 체크된 newTagList 상태를 filter, join 등으로 "VEGAN,ETC" 문자열 형식으로 만들어 search param을 바꿨는데 에러가 났다.

Warning: Cannot update a component (BrowserRouter) while rendering a different component (ChallengeList). To locate the bad setState() call inside ChallengeList, follow the stack trace as described in

ChallengeList를 렌더링하는 동안 BrowserRouter를 업데이트할 수 없습니다. ChallengeList 내부에서 잘못된 setState() 호출을 찾으려면 설명된 대로 스택 추적을 따르세요?!

구글링!

저 코드는 단순히 state만 변경하는 것이 아니라 두 가지? 작업을 하는데,, newTagList가 선택된 태그를 checked: true or false로 바꾼 새로운 tagList 데이터인데 태그가 선택돼서 [...tagList, {checked: true, value: 'ETC', name: '기타'}] 이런 식으로 바뀐 상태면 tagListStr은 'ETC' 가 되고, changeSearchParams란 함수에서 tagList params 부분을 'ETC'로 바꿔준 후 newTagList를 return 해서 tagList state를 변경한다.

setState에서 이전 state 정보를 가지고 변경하면서 url 도 바꿔줘야 하는데 저 함수 밖에서 어떻게 tagList state가 바뀔 때마다 params를 바꿀 수 있을까..? useEffect 아직도 잘 모르겠지만 state가 바뀔 때 사용하는 것은 별로 추천하지 않는다는 글이 생각나서ㅜㅜ

상태를 바꿈과 동시에 router도 변경하려면 useEffect 말곤 방법이 떠오르지 않는다. 그래서 useState가 아니라 그냥 searchParam으로만 관리할 수 있게 작성해 봐야겟다

tagListParam 변수 문자열을 split 해서 tagList 배열로 만들었다

완성코드

export default function ChallengeList() {
  const tagListParam = urlSearchParams.get('tagList') || '';
  const tagList = tagListParam ? tagListParam.split(',') : [];
  
  const changeSearchParams =
    (obj: { name: string; value: string } | [string, string][]) => () => {
      if (Array.isArray(obj)) {
        obj.forEach(([name, value]) => {
          urlSearchParams.set(name, value);
        });
      } else {
        urlSearchParams.set(obj.name, obj.value);
      }
      navigate(`${location.pathname}?${urlSearchParams.toString()}`);
    };

  const handleChageTagList = (value: ChallengeTag) => {
    if (!tagList.includes(value) && tagList.length > 2) return;

    if (tagList.includes(value)) {
      if (tagList.length === 1) {
        clearSearchParams('tagList')();
      } else {
        changeSearchParams({
          name: 'tagList',
          value: tagList.filter((tag) => tag !== value).join(','),
        })();
      }
    } else {
      changeSearchParams({
        name: 'tagList',
        value: [...tagList, value].join(','),
      })();
    }
  };
  
  return (
          <div>
            {openTags &&
              tagsData.map((tag) => (
                <button
                  key={tag.name}
                  onClick={() => handleChageTagList(tag.value as ChallengeTag)}
                  className={`select-none border border-gray-3 rounded-sm px-2 py-1 mr-1 mt-1 text-sm
                    ${
                      tagList.includes(tag.value) ? 'bg-gray-3 text-white' : 'text-gray-4'
                    }
                  `}
                >
                  {tag.name}
                </button>
              ))}
          </div>
  );
}

실수한 부분들!

먼저 tagList searchParams가 저장된 tagListParam 변수에 아무것도 없으면 split을 했을 때 tagList 변수엔 [''] 빈 문자열이 담겨서 [...tagList, value].join(',')을 했을 때 값이 하나라면 'VEGAN'이 아닌 ',VEGAN'으로 바뀌어 제대로 조회할 수 없었다.

그래서
const tagList = tagListParam ? tagListParam.split(',') : [];
tagListParam이 빈 문자열이면 [] 아니면 split을 해서 저장했다

그리고
tagListParam은 문자열이고 tagList는 배열인데 둘 다 include 메서드를 사용할 수 있다. 근데 만약 선택된 tag의 value가 'VEGANRECIPE' 이면 tagListParam.include를 했을 때 VEGAN 값도 true라 선택됐을 때의 css가 같이 적용된다.

그래서 tagListParam을 배열로 바꾼 tagList 변수에 include를 적용해서 태그가 선택됐는지 확인했다.

profile
꾸준히 자유롭게 즐겁게

0개의 댓글