Skip to content

Refactor: 회원가입 페이지 컴포넌트 리팩토링 : RSC, RCC 분리, async-await문으로 리팩토링 #77

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
14 changes: 2 additions & 12 deletions src/apis/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,10 @@ export const getRegister = (code: string) =>
`/users/login/oauth/kakao?code=${code}&redirectURI=${KAKAO_REDIRECT_URI}`
);

export const patchLogout = (accessToken: string, refreshToken: string) =>
publicApi.patch('/users/logout', {
accessToken: `Bearer ${accessToken}`,
refreshToken: `Bearer ${refreshToken}`,
});
export const patchLogout = () => publicApi.patch('/users/logout');

export const patchDeleteAccount = (
accessToken: string,
refreshToken: string,
withdrawalReason: string
) =>
export const patchDeleteAccount = (withdrawalReason: string) =>
publicApi.patch('/users/deleteAccount', {
accessToken: `Bearer ${accessToken}`,
refreshToken: `Bearer ${refreshToken}`,
withdrawalReason,
});

Expand Down
25 changes: 8 additions & 17 deletions src/apis/config/privateApi.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import axios from 'axios';

import { CustomInstance } from './type';
import { ACCESS_TOKEN, ERROR_UNAUTHORIZED, ERROR_UNSUPPORTED_MEDIA_TYPE } from '@/constants';
import { BASE_API_URL } from '@/constants/env';

function getAccesstoken() {
if (typeof window !== 'undefined') {
const item = localStorage.getItem('accesstoken');
return item;
}
}
import { getClientCookie, removeClientCookie } from '@/utils';

const privateApi: CustomInstance = axios.create({
baseURL: `${BASE_API_URL}/api`,
Expand All @@ -18,24 +13,20 @@ const privateApi: CustomInstance = axios.create({
privateApi.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response.status === 401) {
const status = error.response.status;
if (status === ERROR_UNAUTHORIZED || status === ERROR_UNSUPPORTED_MEDIA_TYPE) {
alert('세션이 만료되었어요. 다시 로그인이 필요해요!');
} else {
alert('오류가 발생했어요. 다시 시도해주세요');
removeClientCookie(ACCESS_TOKEN);
}
location.href = '/auth/logout';
location.href = '/';
return Promise.reject(error);
}
);

privateApi.interceptors.request.use(
(config) => {
const accessToken = getAccesstoken();

if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}

const accessToken = getClientCookie(ACCESS_TOKEN);
config.headers.Authorization = `Bearer ${accessToken}`;
return config;
},
(error) => {
Expand Down
5 changes: 1 addition & 4 deletions src/apis/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ export const usePoseFeedQuery = (
);
};

export const useBookmarkFeedQuery = (
accesstoken: string,
options?: UseInfiniteQueryOptions<PoseFeedContents>
) =>
export const useBookmarkFeedQuery = (options?: UseInfiniteQueryOptions<PoseFeedContents>) =>
useSuspenseInfiniteQuery<PoseFeedContents>(
['bookmarkFeed'],
({ pageParam = 0 }) => getBookmarkFeed(pageParam),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치를 간단히 코드 리뷰해드리겠습니다.

고쳐야 할 부분:

  1. useBookmarkFeedQuery 함수의 인자로 accessToken을 요청하는 것이 사라졌는데, 이는 기존 코드와 호환성 문제를 일으킬 수 있습니다.
  2. options 매개변수가 선택적으로 변경되었는데, 해당 변경에 대한 적절한 설명 또는 주석이 필요합니다.

개선 제안:

  1. accesstoken 매개변수가 완전히 없어진 부분에 대한 변경사항을 충분히 문서화하거나 코멘트 달아주면 좋습니다.
  2. 코드 변경에 따른 테스트를 실행하여 기존 동작의 변화를 확인하는 것이 바람직합니다.
  3. 코드베이스 전반적으로 문자열 상수 및 직접 작성한 문자열을 상수로 추출하여 가독성을 향상시킬 수 있습니다.

Expand Down
10 changes: 4 additions & 6 deletions src/app/(Main)/mypose/bookmark/BookmarkSecion.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
'use client';

import BookmarkEmpty from './BookmarkEmpty';
import { useBookmarkFeedQuery } from '@/apis';
import FeedSection from '@/components/Feed/FeedSection';

interface BookmarkSecionI {
accesstoken: string;
}

export default function BookmarkSecion({ accesstoken }: BookmarkSecionI) {
const query = useBookmarkFeedQuery(accesstoken);
export default function BookmarkSecion() {
const query = useBookmarkFeedQuery();

return (
<FeedSection query={query}>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 코드 리뷰 요약:
  • 사용되지 않는 'use client'; 라인 제거 필요
  • BookmarkSecion 컴포넌트의 props 설정에서 accesstoken을 인자로 받도록 되어 있는데, 실제로 이 값을 사용하지 않음
  • BookmarkSecion 컴포넌트가 BookmarkEmpty, FeedSection 컴포넌트를 import하고 있는데, 실제로 사용되는 방식이 아님
  1. 버그 및 개선 제안:
  • 'use client'; 라인은 필요 없으므로 삭제하세요.
  • BookmarkSecion 컴포넌트의 BookmarkSecionI interface와 accesstoken prop은 현재 사용되지 않으므로 제거하는 것이 좋습니다.
  • BookmarkEmptyFeedSection 컴포넌트가 불필요하게 import된 것 같습니다. 사용되지 않는 코드는 정리해 주세요.

좀더 간결하고 효율적인 코드를 위한 변경 사항들을 제안 드렸습니다.

Expand Down
13 changes: 6 additions & 7 deletions src/app/(Main)/mypose/bookmark/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
'use client';

import BookmarkEmpty from './BookmarkEmpty';
import BookmarkSecion from './BookmarkSecion';
import useUserState from '@/context/userState';
import { ACCESS_TOKEN } from '@/constants';
import { getServerCookie } from '@/utils';

export default function BookmarkPage() {
const { token } = useUserState();
export default async function BookmarkPage() {
const token = await getServerCookie(ACCESS_TOKEN);

if (token) return <BookmarkSecion accesstoken={token?.accessToken} />;
else return <BookmarkEmpty />;
if (token) return <BookmarkSecion />;
return <BookmarkEmpty />;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 버그 리스크:

    • 코드 패치에서 import BookmarkSecion from './BookmarkSecion';가 아닌 import BookmarkSection from './BookmarkSection';으로 수정해야 합니다.
    • 반환되는 컴포넌트가 <BookmarkSecion />이지만, 실제로 accessToken prop을 제공하지 않으므로 해당 부분을 확인하고 조치해야 합니다.
  2. 개선 제안:

    • 에러 처리를 추가하여 사용자 토큰 확인 중에 발생하는 예외 상황을 처리할 수 있도록 하십시오.
    • 비동기 함수 BookmarkPage의 오류 처리를 개선하고, 적절한 오류 메시지를 반환하도록 수정하세요.
    • 코드 읽기 쉽게 만들기 위해 변수나 함수명을 명확하게 지정하여 가독성을 높일 필요가 있습니다.

4 changes: 2 additions & 2 deletions src/app/(Main)/talk/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import TalkSection from './components/TalkSection';
import TitleSection from './components/TitleSection';
import TalkSection from './TalkSection';
import TitleSection from './TitleSection';
import { PageAnimation } from '@/components/PageAnimation';

export default function Talk() {
Expand Down
11 changes: 6 additions & 5 deletions src/app/(Sub)/menu/MenuListSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import Link from 'next/link';

import LogoutModal from '@/components/Login/LogoutModal';
import { useOverlay } from '@/components/Overlay/useOverlay';
import { ACCESS_TOKEN } from '@/constants';
import { menuList } from '@/constants/data';
import useUserState from '@/context/userState';
import { getClientCookie } from '@/utils';

export default function MenuListSection() {
const { isLogin } = useUserState();
const token = getClientCookie(ACCESS_TOKEN);
const { open } = useOverlay();

function handleLogout() {
Expand All @@ -30,12 +31,12 @@ export default function MenuListSection() {
<div key={idx} className="py-12" />
)
)}
{isLogin && (
{token && (
<>
<div className="cursor-pointer py-12" onClick={handleLogout}>
<div className="py-12 cursor-pointer" onClick={handleLogout}>
<span id="subtitle-1">로그아웃</span>
</div>
<Link href={'/menu/withdraw'} className="cursor-pointer py-12">
<Link href={'/menu/withdraw'} className="py-12 cursor-pointer">
<span id="subtitle-1" className="text-tertiary">
탈퇴하기
</span>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드의 리뷰를 짧게 작성해 드리겠습니다.

  1. 개선 사항:
  • 클라이언트 측에서 민감한 데이터(예: 액세스 토큰)를 사용하는 것은 보안 위험을 초래할 수 있습니다.
  • handleLogout 함수에 대한 확실한 에러 핸들링이 필요합니다.
  • menuList를 사용하지 않는다면, 해당 import 문은 삭제되어야 합니다. 불필요한 코드로 남아있습니다.
  • 사용되지 않는 LogoutModal import도 삭제하는 것이 좋습니다.
  1. 버그 리스크:
  • 토큰이 sessionStorage나 localStorage에 저장되는 경우, 보안 문제가 발생할 수 있습니다.
  • Link 컴포넌트를 사용할 때 href 속성에 object를 제대로 전달하지 않고 있습니다. 이를 수정해야 합니다.
  1. 일반적인 권장:
  • React 컴포넌트에 대한 단위 테스트(Test Code) 작성이 부족합니다.
  • Consistency를 위해 CSS 클래스 순서를 통일하면 더 읽기 쉬울 수 있습니다.

개선을 위해:

  • 민감한 데이터에 대한 보안 강화
  • 안정적인 에러 핸들링 추가
  • 불필요한 import 및 코드 정리
  • 보안 상 스토리지에 민감한 데이터를 저장할 때 주의

등을 고려하면 좋을 것입니다.

Expand Down
6 changes: 2 additions & 4 deletions src/app/(Sub)/menu/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
'use client';

import MenuListSection from './MenuListSection';
import Header from '@/components/Header';
import LoginSection from '@/components/Login/LoginSection';

export default function MenuPage() {
export default async function MenuPage() {
return (
<div className="px-20">
<Header close={true} title="메뉴" />
<Header close title="메뉴" />
<LoginSection />
<MenuListSection />
</div>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치의 개선점과 잠재적 버그 위험에 대한 간단한 코드 리뷰입니다:

  • use client 라인이 삭제되었습니다. 이것이 의도된 것이라면 적절한 조치입니다.
  • export default async function MenuPage()로 변경되었습니다. 비동기 함수로 변경됨에 따라 추가된 비동기 작업에 대한 주의가 필요할 수 있습니다.
  • <Header close={true} title="메뉴" /> 부분을 <Header close title="메뉴" />로 변경하여 props를 명시적으로 전달하는 대신 생략 기능을 사용하였습니다. 해당 변경은 깔끔하게 읽힙니다.
  • 현재 코드에서 특별한 버그 위험이 보이지는 않습니다. 다만, 컴포넌트 간 데이터 흐름이나 비동기 작업 등에 대한 안정성을 고려해야 할 수 있습니다.
  • 코드 스타일 일관성을 위해 모든 파일에서 같은 따옴표 사용을 유지하는 것이 좋습니다 (예: ' 또는 " 중 하나만 사용).

코드는 보호해야할 특별한 문제점은 없어 보입니다.

Expand Down
37 changes: 37 additions & 0 deletions src/app/auth/AuthComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client';

import { useRouter } from 'next/navigation';

import { getRegister } from '@/apis';
import { ACCESS_TOKEN, EMAIL, NICKNAME } from '@/constants';
import useDidMount from '@/hooks/useDidMount';
import { setClientCookie } from '@/utils';

interface AuthComponentProps {
code: string;
}

export default function AuthComponent({ code }: AuthComponentProps) {
const router = useRouter();

useDidMount(async () => {
try {
const {
token: { accessToken },
email,
nickname,
} = await getRegister(code);

setClientCookie(ACCESS_TOKEN, accessToken);
setClientCookie(EMAIL, email);
setClientCookie(NICKNAME, nickname);

alert(`로그인에 성공했어요!`);
router.push('/');
} catch (error) {
router.push('/');
}
});

return null;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. import 구문에서 'next/navigation'이 아니라 'next/router'를 사용해야 합니다.
  2. useDidMount 대신 useEffect 훅을 사용하여 비동기 작업을 처리할 수 있습니다.
  3. 예외 처리에서 router.push('/')를 계속 반복해서 사용하는 대신, 에러 발생 시 특정 메시지를 보여주는 방안을 고려할 수 있습니다.
  4. 클라이언트 쪽 쿠키 설정에 대한 검증 및 보안을 강화할 필요가 있습니다.
  5. 코드에 주석을 추가하여 기능 및 처리 방법을 명확히 설명하는 것이 좋습니다.

24 changes: 24 additions & 0 deletions src/app/auth/logout/LogoutComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client';

import { useRouter } from 'next/navigation';

import { patchLogout } from '@/apis';
import useUserState from '@/context/userState';
import useDidMount from '@/hooks/useDidMount';

export default function LogoutComponent() {
const { token, clearUser } = useUserState();
const router = useRouter();

useDidMount(async () => {
if (token) {
await patchLogout(token.accessToken, token.refreshToken);
alert('로그아웃 되었습니다');
}
clearUser();
localStorage.removeItem('accesstoken');
router.back();
});

return null;
}
24 changes: 2 additions & 22 deletions src/app/auth/logout/page.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한가지 컴포넌트로 이루어진 페이지를 만드는 이유를 잘 모르겠습니다..!
가능하면 파일 갯수를 줄이고 싶은데, 본 페이지에 LogoutComponent 코드를 넣는건 어려울까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨벤션을 맞추기 위해서 따로 뺐습니다.

모든 페이지는 RSC(React Server Component)로 두고, 그 하위에 RSC와 RCC(React Client Component)로 구분해야 추후 확장에 있어서 RSC와 RCC를 구분하여 컴포넌트를 추가할 수 있기 때문입니다.

만약 이 page.tsx 컴포넌트가 RCC이면, 추후 RSC로 구현이 가능한 컴포넌트가 추가되었을 때 구조를 다시 바꿔야 한다는 불편함이 예상됩니다. 그래서 모든 page.tsx컴포넌트는 RSC로 두고, 하위 컴포넌트에서 RSC와 RCC를 구분하고자 하는 컨벤션을 사용하는 건 어떨까요?

의견 자유롭게 제시해주세요 !!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

모든 페이지를 RSC로 두는 것에 대해서는 찬성합니다..!

폴더구조에 대해서, page.tsx도 하나의 컴포넌트인 만큼 해당 페이지에 종속되는 컴포넌트들은 같은 폴더 내에 (components 폴더를 매번 따로 만들지 않고) 정의하는 방식으로 폴더의 depth를 줄여나가면 더 좋을 것 같습니다!

Copy link
Collaborator Author

@guesung guesung Aug 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

링크

좋습니다 :) 구두로 논의한 방식으로 컨벤션에 추가했습니다 !

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7d2acb3 수정한 커밋입니다 !

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

557e50a 추가된 컨벤션에 맞춰서 리팩토링 진행했습니다!

Original file line number Diff line number Diff line change
@@ -1,25 +1,5 @@
'use client';

import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

import { patchLogout } from '@/apis';
import useUserState from '@/context/userState';
import LogoutComponent from './LogoutComponent';

export default function Page() {
const { token, clearUser } = useUserState();
const router = useRouter();

useEffect(() => {
if (token) {
patchLogout(token.accessToken, token.refreshToken).then(() => {
alert('로그아웃 되었습니다');
});
}
clearUser();
localStorage.removeItem('accesstoken');
router.back();
});

return;
return <LogoutComponent />;
}
30 changes: 10 additions & 20 deletions src/app/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
'use client';

import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
import { redirect } from 'next/navigation';

import { getRegister } from '@/apis';
import useUserState from '@/context/userState';
import AuthComponent from './AuthComponent';

export default function Page() {
const code = useSearchParams().get('code');
const { setUser } = useUserState();
const router = useRouter();

useEffect(() => {
if (code) {
getRegister(code).then((response) => {
setUser(response);
localStorage.setItem('accesstoken', response.token.accessToken);
alert(`로그인에 성공했어요!`);
router.back();
});
}
});
interface AuthPageProps {
searchParams: {
code: string;
};
}

return;
export default function AuthPage({ searchParams: { code } }: AuthPageProps) {
if (!code) redirect('/');
return <AuthComponent code={code} />;
}
32 changes: 32 additions & 0 deletions src/app/auth/withdraw/WithdrawComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import { useRouter, useSearchParams } from 'next/navigation';

import { patchDeleteAccount } from '@/apis';
import { ACCESS_TOKEN } from '@/constants';
import useDidMount from '@/hooks/useDidMount';
import { getClientCookie, removeClientCookie } from '@/utils';

export default function WithdrawComponent() {
const router = useRouter();
const token = getClientCookie(ACCESS_TOKEN);
const withdrawalReason = useSearchParams().get('reason');

useDidMount(async () => {
if (token && withdrawalReason) {
const response = await patchDeleteAccount(withdrawalReason);
console.log(response);
}

// 회원탈퇴 api 될 떄 까지만
// if (token) {
// patchLogout(token.accessToken, token.refreshToken).then(() => {
// alert('로그아웃 되었습니다');
// });
// }
removeClientCookie(ACCESS_TOKEN);
router.replace('/menu');
});

return null;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치의 간단한 코드 리뷰를 제공하겠습니다.

  1. import { useRouter, useSearchParams } from 'next/navigation'; 이 부분에서 'next/navigation' 모듈을 사용하는 것은 잘못된 경로입니다. 올바른 경로인 'next/router'를 사용해야 합니다.

  2. useDidMount 훅은 내장 라이브러리에 없는데, 커스텀 훅으로 추측됩니다. 해당 훅이 어떻게 구현되었는지 점검할 필요가 있습니다.

  3. 비동기 작업을 수행하는 useDidMount 훅 내에서 에러 처리를 하지 않고 있습니다. 작업이 실패하면 적절한 방법으로 처리하는 코드가 빠져있습니다.

  4. patchLogout 함수가 정의되어 있지 않아 별도의 로그아웃 처리가 이루어지지 않습니다. 이에 관련된 수정이 필요합니다.

  5. router.replace('/menu');는 회원 탈퇴 직후에 실행되므로 사용자 경험 면에서 즉각적인 리디렉션에 대한 고민이 필요할 수 있습니다.

  6. 보안 측면에서 클라이언트 쿠키 값을 단순히 삭제하기만 하는 것보다 안전한 방식으로 처리하는 것이 좋습니다.

  7. 코드 리팩터링 시, 주석에 오타가 있습니다. "때"의 한자 표기 오류입니다.

  8. WithdrawComponent 함수는 JSX 엘리먼트를 반환하지 않음에 유의해야 합니다. 기능적으로 return null;이 의도대로 동작하는지 확인할 필요가 있습니다.

개선 사항:

  • 모듈 경로 수정 (next/navigation -> next/router)
  • 비동기 작업의 에러 처리 추가
  • patchLogout 함수 관련 처리 추가
  • 클라이언트 쿠키의 안전한 삭제 처리 추가
  • 사용자 리디렉션 부분에 대한 더 나은 해결책 검토 및 적용
  • useDidMount 훅의 구체적인 구현 리뷰 및 개선

주의: 위의 제안은 코드 리뷰의 일부로, 실제 상황에 맞게 구체적으로 작업을 진행해야 합니다.

33 changes: 2 additions & 31 deletions src/app/auth/withdraw/page.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,5 @@
'use client';

import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';

import { patchDeleteAccount, patchLogout } from '@/apis';
import useUserState from '@/context/userState';
import WithdrawComponent from './WithdrawComponent';

export default function Page() {
const router = useRouter();
const { token, clearUser } = useUserState();
const withdrawalReason = useSearchParams().get('reason');

useEffect(() => {
if (token && withdrawalReason) {
patchDeleteAccount(token.accessToken, token.refreshToken, withdrawalReason).then(
(response) => {
console.log(response);
}
);
}
// 회원탈퇴 api 될 떄 까지만
// if (token) {
// patchLogout(token.accessToken, token.refreshToken).then(() => {
// alert('로그아웃 되었습니다');
// });
// }
clearUser();
localStorage.removeItem('accesstoken');
router.replace('/menu');
});

return;
return <WithdrawComponent />;
}
5 changes: 3 additions & 2 deletions src/components/Feed/BookmarkButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ import { Icon } from '../Button/Icon';
import { deleteBookmark, postBookmark } from '@/apis';
import LoginModal from '@/components/Login/LoginModal';
import { useOverlay } from '@/components/Overlay/useOverlay';
import { ACCESS_TOKEN } from '@/constants';
import { ICON } from '@/constants/icon';
import useUserState from '@/context/userState';
import { getClientCookie } from '@/utils';

interface BookmarkButtonI {
poseId: number;
isMarked: boolean;
}
export default function BookmarkButton({ poseId, isMarked }: BookmarkButtonI) {
const { open } = useOverlay();
const { token } = useUserState();
const token = getClientCookie(ACCESS_TOKEN);
const [marked, setMarked] = useState(isMarked);

function onClick() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 리뷰:

  1. import 구문에서는 상대 경로(@/) 대신 모듈의 절대 경로를 사용하는 것이 좋습니다.
  2. BookmarkButtonI 인터페이스의 이름은 구체적이라서 이해하기 쉽습니다.
  3. BookmarkButton 함수 컴포넌트에서 token을 가져오는 방식은 개선이 필요합니다. useUserState를 사용하던 대신 직접적인 방법을 택함으로써 코드의 의존성과 가독성을 높일 수 있습니다.
  4. useState를 사용하여 marked, setMarked를 선언한 것이 좋습니다.
  5. onClick 함수가 선언되어 있지만, 해당 함수가 실제로 어떻게 구현되는지는 코드 스니펫에 없습니다. 이 부분을 확인해 주세요.
  6. 쿠키 관련 작업을 수행하는 함수인 getClientCookie와 토큰 관련 상수인 ACCESS_TOKEN이 모듈 외부에서 가져올 때 어떻게 동작하는지에 따라 보안 및 에러 처리를 고민하셔야 합니다.

Expand Down
Loading