몽충이 짓. study category icon tab list 작업 중 억까당해서 하루 날린 썰

AREUM·2024년 9월 25일
0

멍청한 짓

목록 보기
6/6
post-thumbnail

1. 현재 상황

  • study page에서 StudyCategoryTabButtonList의 컴포넌트를 만들어 필요의 따른 아이콘들을 list로 보여주는 컴포넌트를 재사용성을 위해
    - label, value를 담은 배열
    - icon을 담은 배열
    만들어 매개변수로 넘겨줬다.

/* src/app/(route)/study/page.tsx > StudyComponent */

import { GOALS, ONOFF } from "@/dummies/categories";
import StudyCategoryTabButtonList from "./_components/StudyCategoryTabButtonList";
import {
  ONOFFICONS,
  STUDYCATEGORYICONS,
} from "./_components/icon/StudyCategoryIcons";

<div className="flex justify-between text-[#C2C3C4]">
  <StudyCategoryTabButtonList
    LABEL_VALUE={GOALS}
	ICONS={STUDYCATEGORYICONS}
  />
  <StudyCategoryTabButtonList
	  LABEL_VALUE={ONOFF}
	  ICONS={ONOFFICONS}
  />
</div>

label, value를 담은 배열

/* src/dummies/categories.ts */

export const GOALS = [
  { label: "개념학습", value: "goal_1" },
  { label: "응용/활용", value: "goal_2" },
  { label: "프로젝트", value: "goal_3" },
  { label: "자격증/시험", value: "goal_4" },
  { label: "취업/면접", value: "goal_5" },
  { label: "챌린지", value: "goal_6" },
  { label: "특강", value: "goal_7" },
  { label: "취미", value: "goal_8" },
];

export const ONOFF = [
  { label: "오프라인", value: "offline" },
  { label: "온라인", value: "online" },
];

icon를 담은 배열

/* src/app/(route)/study/_components/icon/StudyCategoryIcons.tsx */

import {
  BellRingIcon,
  BuildingIcon,
  BulbIcon,
  FileEditIcon,
  KeyboardIcon,
  MonitorPlayIcon,
  NotebookIcon,
  OfflineIcon,
  OnlineIcon,
  PuzzleIcon,
  TIconStylingProps,
} from "@/common/Atoms/Image/Icon";

export const STUDYCATEGORYICONS = [
  (props: TIconStylingProps) => <NotebookIcon {...props} />,
  (props: TIconStylingProps) => <KeyboardIcon {...props} />,
  (props: TIconStylingProps) => <BulbIcon {...props} />,
  (props: TIconStylingProps) => <FileEditIcon {...props} />,
  (props: TIconStylingProps) => <BuildingIcon {...props} />,
  (props: TIconStylingProps) => <BellRingIcon {...props} />,
  (props: TIconStylingProps) => <MonitorPlayIcon {...props} />,
  (props: TIconStylingProps) => <PuzzleIcon {...props} />,
];

export const ONOFFICONS = [
  (props: TIconStylingProps) => <OfflineIcon {...props} />,
  (props: TIconStylingProps) => <OnlineIcon {...props} />,
];

아이콘의 컬러를 자유롭게 바꾸기위해 아이콘을 함수로 만들어서 props로 전달하는 형식이다.

StudyCategoryTabButtonList 컴포넌트

"use client";

import { TabButton } from "@/app/_components/TabButton";
import { TIconStylingProps } from "@/common/Atoms/Image/Icon";
import React from "react";
import { useState } from "react";

type TStudyCategoryValue = {
  label: string;
  value: string;
};
type TStudyCategoryIcon = (props: TIconStylingProps) => JSX.Element;

export default function StudyCategoryTabButtonList({
  LABEL_VALUE,
  ICONS,
}: {
  LABEL_VALUE: TStudyCategoryValue[];
  ICONS: TStudyCategoryIcon[];
}) {
  const STUDYCATEGORY_TAB = LABEL_VALUE.map((category, index) => ({
    ...category,
    Icon: ICONS[index],
  }));

  const [select, setSelected] = useState(LABEL_VALUE[0].value);

  return (
    <div className="flex gap-4 w-fit mb-11">
      {STUDYCATEGORY_TAB.map(({ label, value, Icon }) => {
        const active = select === value;
        return (
          <TabButton
            key={value}
            label={label}
            active={active}
            onClick={() => setSelected(value)}
          >
            <Icon strokeColor={active ? "#FFF" : undefined} />
          </TabButton>
        );
      })}
    </div>
  );
}

❗️체크 포인트❗️
1. type도 지정을 잘 했다.
2. 값도 전달을 잘 했다.
3. 하지만, 억까가 시작되었다.

2. 문제의 억까 (문제 원인찾기 분석)

그렇게 새벽4시반까지 원인을 찾아 헤매면서 같은 상황인 글을 발견했다.

원인

함수의 기능을 전달하는 것이 문제가 되었다.
그래서 기능을 전달하는 것이 아닌, 데이터를 전달하는 형식으로 변경해야했다.

아이콘의 컬러를 변경하도록 하기위해 컴포넌트화로 만들었는데,
그 함수 컴포넌트로 만들어놓은 아이콘의 함수들이 문제가 되었던 것이었다.

3. 해결하기

1번째 방법

아이콘을 한 곳에 모아둔 컴포넌트 파일인 StudyCategoryIcons 파일에 "use client"를 붙히는 방법으로 해결했다.

하지만, 이 방법을 사용해 함수를 사용한 Icon의 배열들도 같은 client component로 변경이 되어야하는 것을 알았다.

2번째 방법

1번째 방법을 조금 개선하고자 client component로 만들지 않는 방법을 선택했다.

보기 쉽게 두 코드를 비교하자면,

/* src/app/(route)/study/_components/icon/StudyCategoryIcons.tsx */

import {
  NotebookIcon,
} from "@/common/Atoms/Image/Icon";

/* 변경 전 코드 */
export const STUDYCATEGORYICONS = [
  (props: TIconStylingProps) => <NotebookIcon {...props} />
  // ....
]

/* 변경 후 코드 */
export const STUDYCATEGORYICONS = [
  NotebookIcon,
  // ....
]
"use client";

import { TabButton } from "@/app/_components/CategoryTab/TabButton";
import { CategoryTabIcon } from "@/app/_components/CategoryTab/TabIcons";
import useQueryString from "@/hooks/useQueryString";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import React, { useEffect, useState } from "react";

type TStudyCategoryValue = {
  label: string;
  value: string;
};

export default function StudyCategoryTabButtonList({
  queryKey,
  categoryName,
  categoryIcons,
}: {
  queryKey: string;
  categoryName: TStudyCategoryValue[];
  categoryIcons: string[];
}) {
  const params = useSearchParams();
  const [select, setSelected] = useState(params?.get(queryKey) || "");

  const studyCategoryIcons = categoryName.map((category, index) => ({
    ...category,
    iconName: categoryIcons[index],
  }));

  const onChangeQuery = useQueryString({
    paramsKey: queryKey,
    queryInclude: "search",
  });

  const clickHandler = (value: string) => {
    setSelected((prev) => {
      if (prev === value) {
        onChangeQuery("");
        return "";
      }
      onChangeQuery(value);
      return value;
    });
  };

  return (
    <div className="flex gap-4 w-fit mb-11">
      {studyCategoryIcons.map(({ label, value, iconName }) => {
        const active = select === value;
        return (
          <TabButton
            key={value}
            label={label}
            active={active}
            onClick={() => clickHandler(value)}
          >
            <CategoryTabIcon
              name={iconName}
              strokeColor={active ? "#FFF" : undefined}
            />
          </TabButton>
        );
      })}
    </div>
  );
}

추가 변경 & 개선
1. Type을 함수가 아닌 string으로 변경
2. queryKey를 조건으로 icon이 클릭되는지 여부를 판단

해결 후, 느낀점

배열에 담은 icon에 필요한 값들을 콜백함수를 사용하지 않고 string형식으로 변경해 “use client”를 사용을 줄이는 방향으로 변경했다.

profile
어깨빵으로 부딪혀보는 개발끄적이는 양씨 인간

0개의 댓글