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. 하지만, 억까가 시작되었다.
그렇게 새벽4시반까지 원인을 찾아 헤매면서 같은 상황인 글을 발견했다.
함수의 기능을 전달하는 것이 문제가 되었다.
그래서 기능을 전달하는 것이 아닌, 데이터를 전달하는 형식으로 변경해야했다.
아이콘의 컬러를 변경하도록 하기위해 컴포넌트화로 만들었는데,
그 함수 컴포넌트로 만들어놓은 아이콘의 함수들이 문제가 되었던 것이었다.
아이콘을 한 곳에 모아둔 컴포넌트 파일인 StudyCategoryIcons
파일에 "use client"
를 붙히는 방법으로 해결했다.
하지만, 이 방법을 사용해 함수를 사용한 Icon
의 배열들도 같은 client component
로 변경이 되어야하는 것을 알았다.
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”
를 사용을 줄이는 방향으로 변경했다.