23.08.03
오늘 한 일
- 카공실록 장소 상세페이지 서버 컴포넌트 분리
- gloddy 모임 상세페이지 api 연결
카공실록 장소 상세페이지 서버 컴포넌트 분리
이전에는 page.tsx
파일에 UI와 비즈니스 로직이 섞여있었다. 그래서 역할에 따라 컴포넌트를 분리했다.
Suspensive 라이브러리 사용
gloddy와 카공실록 두 프로젝트에서 Suspensive 라이브러리를 도입하자고 제안했다. 이 라이브러리를 사용하면 코드를 더욱 선언적으로 작성할 수 있어서 좋다.
다들 도입에 대해 흔쾌히 동의해서 바로 적용해보았다.
적용해보기
커스텀 쿼리 훅을 만들 때 useQuery
대신 useSuspenseQuery
를 사용한다.
// apis/place/queries.ts
export function useGetPlace(id: number) {
return useSuspenseQuery(Keys.place(id), () => getPlace(id));
}
나는 서버컴포넌트(페이지)에서 데이터를 prefetch하고 있다. suspense로 감싸고 그 위에 errorboundary를 감싸서 에러를 잡으려고 했다.
react-query에서 QueryErrorResetBoundary
를 제공하는데, 이 컴포넌트를 사용하면 에러가 발생했을 때 캐시를 리셋할 수 있는 UI를 띄울 수 있다.
하지만 사용할 떄 꽤나 복잡해 보일 수 있다. QueryErrorResetBoundary + ErrorBoundary
가 기본이고, 추가로 Suspense까지 사용한다면 정신 없다..
이런 문제를 Suspensive 라이브러리에서 해결해주고 있다.
QueryErrorBoundary
는 QueryErrorResetBoundary + ErrorBoundary
와 동일하고,
QueryAsyncBoundary
는 QueryErrorResetBoundary + ErrorBoundary + Suspense
와 동일하다.
AsyncBoundary
라는 컴포넌트도 제공하고 있는데, ErrorBoundary + Suspense
와 동일하기 때문에
결국 QueryAsyncBoundary
는 QueryErrorBoundary + AsyncBoundary
이다.
import PlaceDetail from './components/PlaceDetail';
import { Keys, getPlace } from '@/apis/place';
import { HydrationProvider } from '@/providers/HydrationProvider';
import { QueryAsyncBoundary } from '@suspensive/react-query';
export default async function PlacePage({ params }: { params: { id: string } }) {
const placeId = Number(params.id);
return (
<QueryAsyncBoundary
pendingFallback={<div>로딩중</div>}
rejectedFallback={({ error, reset }) => (
<div>
<div>에러가 발생했습니다.</div>
<button onClick={reset}>재시도</button>
<pre>{error.message}</pre>
</div>
)}
>
<HydrationProvider queryKey={Keys.place(placeId)} queryFn={() => getPlace(placeId)}>
<PlaceDetail placeId={placeId} />
</HydrationProvider>
</QueryAsyncBoundary>
);
}
이제 데이터를 가져올 때 별다른 분기처리 없이 data를 가져오면 된다.
// components/PlaceDetail.tsx
export default function PlaceDetail({ placeId }: PlaceDetailProps) {
const { data: placeData } = useGetPlace(placeId); // placeData: Place
return (
// ...
);
}
위와 같이 코드를 작성하면 에러가 발생하게 된다.
Uncaught Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
함수를 "use server"로 표시해서 명시적으로 작성한 경우 말고는 직접 클라이언트 컴포넌트에 전달할 수 없다고 한다.
해결 방법
1. rejectedFallback에 들어갈 fallback 컴포넌트 만들기
fallback 클라이언트 컴포넌트를 만들어서 rejectedFallback
에 전달해주면 된다.
2. "use server" 추가
"use serve"를 추가하기 위해서는 next config 파일에 다음과 같이 설정해야한다.
// next.config.js
module.exports = {
experimental: {
serverActions: true,
},
};
<QueryAsyncBoundary
pendingFallback={<div>로딩중</div>}
rejectedFallback={async ({ error, reset }) => {
'use server';
return (
<div>
<div>에러가 발생했습니다.</div>
<button onClick={reset}>재시도</button>
<pre>{error.message}</pre>
</div>
);
}}
>
3. QueryAsyncBoundary
를 커스텀 컴포넌트로 만들기
커스텀 클라이언트 컴포넌트로 만들어서 사용하면 된다.
내일 할 일
- gloddy 모임 멤버 상세정보 페이지 퍼블리싱