Skip to content

Commit

Permalink
[#18] useQuery, useInfiniteQuery -> useSuspenseQuery, useSuspenseInfi…
Browse files Browse the repository at this point in the history
…niteQuery 교체 (#19)

* 🔨 refactor: useQuery를 useSuspenseQuery로 교체 및 error 처러 코드 삭제

* 🔨 refactor: 로딩 UI 및 에러 처리 코드 삭제

* 🔨 refactor: suspense 적용 및 error boundy fallback에 workspace 에러 페이지 설정

* 🔨 refactor: useInfinityQuery -> useSuspenseQuery로 교체

* 🙀 chore: 400 에러와 404에러의 에러 코드 및 상태 메세지가 잘못 설정 되어 있어 올바르게 수정

* 🐛 fix: 기존 에러 발생 시 500 에러로만 응답하는 문제 해결

* 🔨 refactor: workspaceContainer에서 워크스페이스 데이터를 렌더링하고 페칭하는 부분을 분리함

* 🙀 chore: 컴포넌트 분리에 따른 코드 수정

* 🙀 chore: github action 에서 파일명 변경을 감지하지 못한 문제로 인해 파일명 변경

* 🙀 chore: 불필요한 console.log 삭제
  • Loading branch information
lee0jae330 authored Jan 15, 2025
1 parent b33616e commit 0163d1f
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 63 deletions.
12 changes: 7 additions & 5 deletions apps/client/src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ErrorPage, WorkspaceErrorPage } from '@/pages';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import { ToasterWithMax } from '@/shared/ui';
import { ErrorPage } from '@/pages/ErrorPage/ErrorPage';
import { lazy, Suspense } from 'react';
import { Loading } from '@/shared/ui';
import { Suspense, lazy } from 'react';

import { Helmet } from 'react-helmet-async';
import { Loading } from '@/shared/ui';
import { ToasterWithMax } from '@/shared/ui';

// lazy 로딩
const HomePage = lazy(() =>
Expand Down Expand Up @@ -47,12 +48,13 @@ const router = createBrowserRouter([
<title>BooLock - 작업 공간</title>
<meta name="description" content={`워크스페이스에서 HTML과 CSS를 연습해보세요.`} />
</Helmet>

<Suspense fallback={<Loading />}>
<WorkspacePage />
</Suspense>
</>
),
errorElement: <ErrorPage />,
errorElement: <WorkspaceErrorPage />,
},
{
path: '*',
Expand Down
4 changes: 2 additions & 2 deletions apps/client/src/pages/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Banner, HomeHeader, WorkspaceContainer, WorkspaceModal } from '@/widgets';
import { Banner, HomeHeader, WorkspaceModal, WorkspaceSection } from '@/widgets';
import { useClassBlockStore, useLoadingStore, useWorkspaceStore } from '@/shared/store';

import { Loading } from '@/shared/ui';
Expand Down Expand Up @@ -26,7 +26,7 @@ export const HomePage = () => {
<div className="flex h-full w-full flex-col items-center">
<HomeHeader />
<Banner />
<WorkspaceContainer />
<WorkspaceSection />
<WorkspaceModal />
</div>
</>
Expand Down
14 changes: 14 additions & 0 deletions apps/client/src/pages/WorkspaceErrorPage/WorkspaceErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ErrorPage, NotFound } from '@/pages';

import toast from 'react-hot-toast';
import { useRouteError } from 'react-router-dom';

export const WorkspaceErrorPage = () => {
const error: any = useRouteError();
const statusCode = error?.response?.statusCode || error?.status;
if (statusCode === 404) {
toast.error('워크스페이스 정보 불러오기 실패');
return <NotFound />;
}
return <ErrorPage />;
};
16 changes: 5 additions & 11 deletions apps/client/src/pages/Workspacepage/WorkspacePage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { ImageTagModal, CoachMark, WorkspaceContent, WorkspacePageHeader } from '@/widgets';
import { CoachMark, ImageTagModal, WorkspaceContent, WorkspacePageHeader } from '@/widgets';
import { useEffect, useLayoutEffect } from 'react';
import { useGetWorkspace, usePreventLeaveWorkspacePage } from '@/shared/hooks';
import { Loading } from '@/shared/ui';
import { NotFound } from '@/pages/NotFound/NotFound';
import { useParams } from 'react-router-dom';
import { useLayoutEffect, useEffect } from 'react';

import { useCoachMarkStore } from '@/shared/store/useCoachMarkStore';
import { useParams } from 'react-router-dom';

/**
*
Expand All @@ -13,7 +12,7 @@ import { useCoachMarkStore } from '@/shared/store/useCoachMarkStore';
*/
export const WorkspacePage = () => {
const { workspaceId } = useParams();
const { isPending, isError } = useGetWorkspace(workspaceId as string);
useGetWorkspace(workspaceId as string);
usePreventLeaveWorkspacePage();
const { currentStep, isCoachMarkOpen, openCoachMark } = useCoachMarkStore();
const toolboxDiv = document.querySelector('.blocklyToolboxDiv');
Expand All @@ -36,14 +35,9 @@ export const WorkspacePage = () => {
}
}, [currentStep, toolboxDiv]);

if (isError) {
return <NotFound />;
}

return (
<>
<div className="flex h-screen flex-col">
{isPending && <Loading />}
{isCoachMarkOpen && <CoachMark />}
<WorkspacePageHeader />
<WorkspaceContent />
Expand Down
2 changes: 2 additions & 0 deletions apps/client/src/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { HomePage } from './HomePage/HomePage';
export { NotFound } from './NotFound/NotFound';
export { WorkspacePage } from './Workspacepage/WorkspacePage';
export { ErrorPage } from './ErrorPage/ErrorPage';
export { WorkspaceErrorPage } from './WorkspaceErrorPage/WorkspaceErrorPage';
21 changes: 5 additions & 16 deletions apps/client/src/shared/hooks/queries/useGetWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import { createUserId, getUserId, removeCssClassNamePrefix } from '@/shared/util
import {
useClassBlockStore,
useCssPropsStore,
useImageModalStore,
useResetCssStore,
useWorkspaceChangeStatusStore,
useWorkspaceStore,
useImageModalStore,
} from '@/shared/store';

import { WorkspaceApi } from '@/shared/api';
import toast from 'react-hot-toast';
import { useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useSuspenseQuery } from '@tanstack/react-query';
import { workspaceKeys } from '@/shared/hooks';

export const useGetWorkspace = (workspaceId: string) => {
Expand All @@ -24,29 +23,19 @@ export const useGetWorkspace = (workspaceId: string) => {
const { resetChangedStatusState } = useWorkspaceChangeStatusStore();
const { setIsResetCssChecked } = useResetCssStore();
const { setInitialImageMap, setInitialImageList } = useImageModalStore();
const { data, isPending, isError } = useQuery({
const { data, isPending, isError } = useSuspenseQuery({
queryKey: workspaceKeys.detail(workspaceId),
queryFn: () => {
resetChangedStatusState();
return workspaceApi.getWorkspace(userId, workspaceId);
},
});

useEffect(() => {
resetChangedStatusState();
}, []);

useEffect(() => {
if (isError) {
toast.error('워크스페이스 정보 불러오기 실패');
return;
}
if (!data) {
if (!isError || !data || !data.workspaceDto) {
return;
}

if (!data.workspaceDto) {
return;
}
setName(data.workspaceDto.name);
Object.keys(data.workspaceDto.totalCssPropertyObj).forEach((className) => {
createCssClassBlock(className);
Expand Down
7 changes: 4 additions & 3 deletions apps/client/src/shared/hooks/queries/useGetWorkspaceList.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { WorkspaceApi } from '@/shared/api';
import { createUserId, getUserId } from '@/shared/utils';
import { useInfiniteQuery } from '@tanstack/react-query';

import { WorkspaceApi } from '@/shared/api';
import { useSuspenseInfiniteQuery } from '@tanstack/react-query';
import { workspaceKeys } from '@/shared/hooks';
export const useGetWorkspaceList = () => {
const workspaceApi = WorkspaceApi();
Expand All @@ -12,7 +13,7 @@ export const useGetWorkspaceList = () => {
isFetchingNextPage,
isError,
data: workspaceList,
} = useInfiniteQuery({
} = useSuspenseInfiniteQuery({
queryKey: workspaceKeys.list(),
queryFn: async ({ pageParam }) => {
const isNewUser = !getUserId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const meta: Meta<typeof WorkspaceContainer> = {
title: 'widgets/home/WorkspaceContainer',
component: WorkspaceContainer,
parameters: {
layout: 'fullscreen',
layout: 'centered',
},
tags: ['autodocs'],
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { EmptyWorkspace, WorkspaceGrid, WorkspaceHeader, WorkspaceList } from '@/widgets';
import { EmptyWorkspace, WorkspaceGrid, WorkspaceList } from '@/widgets';
import { useGetWorkspaceList, useInfiniteScroll, useVirtualScroll } from '@/shared/hooks';

import { SkeletonWorkspaceList } from '@/shared/ui';
import { TWorkspace } from '@/shared/types';
import { WorkspaceLoadError } from '@/entities';

/**
*
* @description
* 워크스페이스 헤더와 그리드를 감싸는 컨테이너 컴포넌트
* 워크스페이스 리스트를 렌더링하는 컨테이너 컴포넌트
*/
export const WorkspaceContainer = () => {
const { hasNextPage, fetchNextPage, isPending, isFetchingNextPage, isError, workspaceList } =
const { hasNextPage, fetchNextPage, isPending, isFetchingNextPage, workspaceList } =
useGetWorkspaceList();

const { renderedData, offsetY, totalHeight } = useVirtualScroll<TWorkspace>({
Expand All @@ -33,17 +32,8 @@ export const WorkspaceContainer = () => {
const nextFetchTargetRef = useInfiniteScroll({ intersectionCallback: fetchCallback });

return (
<section className="w-full max-w-[1152px] px-3 pb-48">
<WorkspaceHeader />
{isPending && (
<WorkspaceGrid>
<SkeletonWorkspaceList skeletonNum={8} />
</WorkspaceGrid>
)}
{isError ? (
<WorkspaceLoadError />
) : (
workspaceList &&
<>
{workspaceList &&
(workspaceList.length === 0 ? (
<EmptyWorkspace />
) : (
Expand All @@ -57,11 +47,10 @@ export const WorkspaceContainer = () => {
{isFetchingNextPage && <SkeletonWorkspaceList skeletonNum={8} />}
</WorkspaceGrid>
</div>
))
)}
))}
{!isPending && !isFetchingNextPage && hasNextPage && (
<div ref={nextFetchTargetRef} className="h-3 w-full"></div>
)}
</section>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Meta, StoryObj } from '@storybook/react';

import { WorkspaceSection } from './WorkspaceSection';

const meta: Meta<typeof WorkspaceSection> = {
title: 'widgets/home/WorkspaceSection',
component: WorkspaceSection,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
};

export default meta;

type Story = StoryObj<typeof WorkspaceSection>;

export const Default: Story = {
args: {
// propsname: value,
},
};
20 changes: 20 additions & 0 deletions apps/client/src/widgets/home/WorkspaceSection/WorkspaceSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { WorkspaceContainer, WorkspaceHeader } from '@/widgets';

import { SkeletonWorkspaceList } from '@/shared/ui';
import { Suspense } from 'react';

/**
*
* @description
* 워크스페이스 헤더와 컨테이너를 합친 섹션 컴포넌트
*/
export const WorkspaceSection = () => {
return (
<section className="w-full max-w-[1152px] px-3 pb-48">
<WorkspaceHeader />
<Suspense fallback={<SkeletonWorkspaceList skeletonNum={8} />}>
<WorkspaceContainer />
</Suspense>
</section>
);
};
3 changes: 2 additions & 1 deletion apps/client/src/widgets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ export { WorkspaceList } from './home/WorkspaceList/WorkspaceList';
export { WorkspaceHeader } from './home/WorkspaceHeader/WorkspaceHeader';
export { EmptyWorkspace } from './home/EmptyWorkspace/EmptyWorkspace';
export { WorkspaceGrid } from './home/WorkspaceGrid/WorkspaceGrid';
export { WorkspaceContainer } from './home/WorkspaceContainer/WorkspaceContainer';
export { WorkspaceSection } from './home/WorkspaceSection/WorkspaceSection';
export { WorkspaceModal } from './home/WorkspaceModal/WorkspaceModal';
export { WorkspaceContainer } from './home/WorkspaceContainer/WorkspaceContainer';

export { PreviewBox } from './workspace/PreviewBox/PreviewBox';
export { CoachMark } from './workspace/CoachMark/CoachMark';
Expand Down
23 changes: 21 additions & 2 deletions apps/server/src/middlewares/errorMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { Request, Response, NextFunction } from 'express';
import { CustomError } from '../utils/customError';
import {
BadRequestError,
CustomError,
ForbiddenError,
UnauthorizedError,
} from '../utils/customError';
import { NextFunction, Request, Response } from 'express';

import { NotFound } from '@aws-sdk/client-s3';
import { errorStatus } from '../utils/constants';

// eslint-disable-next-line no-unused-vars
Expand All @@ -19,4 +26,16 @@ const errorHandlers: { [key: string]: (err: any, res: Response) => void } = {
CustomError: (err: CustomError, res: Response) => {
res.status(err.statusCode).json({ message: err.message });
},
NotFoundError: (err: NotFound, res: Response) => {
res.status(errorStatus.HTTP_404_NOT_FOUND).json({ message: err.message });
},
BadRequestError: (err: BadRequestError, res: Response) => {
res.status(errorStatus.HTTP_400_BAD_REQUEST).json({ message: err.message });
},
UnauthorizedError: (err: UnauthorizedError, res: Response) => {
res.status(errorStatus.HTTP_401_UNAUTHORIZED).json({ message: err.message });
},
ForbiddenError: (err: ForbiddenError, res: Response) => {
res.status(errorStatus.HTTP_403_FORBIDDEN).json({ message: err.message });
},
};
8 changes: 4 additions & 4 deletions apps/server/src/utils/customError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ export class CustomError extends Error {
}

export class NotFoundError extends CustomError {
constructor(message = 'Bad request') {
super(message, errorStatus.HTTP_400_BAD_REQUEST);
constructor(message = 'Resource not found') {
super(message, errorStatus.HTTP_404_NOT_FOUND);
}
}

export class BadRequestError extends CustomError {
constructor(message = 'Resource not found') {
super(message, errorStatus.HTTP_404_NOT_FOUND);
constructor(message = 'Bad request') {
super(message, errorStatus.HTTP_400_BAD_REQUEST);
}
}

Expand Down

0 comments on commit 0163d1f

Please sign in to comment.