The following dependencies are missing in your queryKey:
eslint@tanstack/query/exhaustive-deps
음.. 일단 공식문서에서는 해당 내용에 대해서 아래와 같이 설명하고 있다.
쿼리 키는 쿼리 함수에 대한 종속성 배열처럼 표시되어야 합니다. queryFn 내부에서 사용되는 모든 변수는 쿼리 키에 추가되어야 합니다. 이렇게 하면 쿼리가 독립적으로 캐시되고 변수가 변경될 때 쿼리가 자동으로 다시 가져오게 됩니다.
그러니까 쿼리 함수를 가지고와서 useQuery를 사용할 때 쿼리 함수에 전달하는 인자에 대한 정보도 쿼리키에 종속성 배열처럼 넣어주라는 의미인 것 같다.
그래서 아래와 같이 사용하면 동작은 하지만 eslint 에러가 발생한다.
const { data } = useQuery({
queryKey: ['comments'],
queryFn: () => fetchComments(post.id),
staleTime: 2000
})
그래서 쿼리 키에 post.id 의 정보를 전달해주어야 한다.
const { data } = useQuery({
queryKey: ['comments', post.id],
queryFn: () => fetchComments(post.id),
staleTime: 2000
})
그런데 이 부분을 타입 스크립트로 사용하면 post.id를 종속성 배열에 전달해주지 않아도 된다.
const { data } = useQuery<PostDetailType[]>({
queryKey: ['comments'],
queryFn: () => fetchComments(post.id),
staleTime: 2000
})
즉, 이렇게 사용이 가능하다. 이렇게 사용해도 eslint 에러가 발생하지 않는다. 이유가 뭘까? 미리 useQuery에게
PostDetailType을 전달해주었기 때문에 fetchComments에 전달되는 postId라는 인자를 미리 알 수 있는 것이고, 그렇기 때문에 굳이 전달해주지 않아도 되는 게 아닐까?
그럼 타입스크립트를 사용할 때는 굳이 전달해주지 않아도 될까? 아니면 린트 에러가 발생하지 않더라도 전달하는 습관을 들이는 게 좋을까? 물론 이게 엄청 중요한 고민거리는 아니다. 적어도 쿼리 키에 대해서 쿼리 함수로 전달되는 모든 정보가 포함되어 있어야 한다는 기본적인 룰만 인지하고 있으면 되고 타입스크립트를 사용하게 되면 그 부분을 좀 더 가독성 좋게 표현할 수 있다는 정도로 이해하면 될 것 같다.
리액트 쿼리로 불러오는 data와 관련해서 Suspense를 사용하고 싶다면 useQuery에 전달하는 키 중에 suspense를 true로 설정해야 한다. 하지만 현재 v5에서는 suspense 키가 useQuery에서 없어졌다.
기존에는 리액트 쿼리로 data를 받아오는 과정에서 리액트 쿼리의 isLoading을 로딩 상태를 적절하게 처리하는 방식을 사용했었다. 그런데 리액트의 Suspense를 알고 난 다음부터는 좀 더 로딩을 처리하고자 할 때 isLoading보다는 가독성도 좋아보이고, 뭔가 로딩 상태를 선언적으로 관리한다는 느낌이 들어서 Suspense를 사용하고자 했다.
그래서 전체적으로 로딩 상태를 보여주기보다는 데이터를 불러오는 곳에서만 로딩 컴포넌트를 보여주기 위해서 리액트 쿼리로 불러오는 data 부분을 Suspense로 감싸서 확인해보았다. 하지만, 로딩 컴포넌트가 동작하지 않았다. 물론 isLoading으로 하면 정상적으로 동작했다. 그럼 Suspense를 사용하는 것 자체가 문제라는 것이었다.
<Suspense fallback={<Loading message="블로그 데이터 로딩 중입니다!!" />}>
{data?.map((post) => (
<li
key={post.id}
className="post-title"
onClick={() => setSelectedPost(post)}
style={{ cursor: 'pointer' }}
>
{post.title}
</li>
))}
</Suspense>
그래서 컴포넌트를 감싸주지 않았기 때문일까 해서 data 부분을 컴포넌트로 만든다음 다시 시도해보았다.
<Suspense fallback={<Loading message="블로그 데이터 로딩 중입니다!!" />}>
<PostItem data={data} setSelectedPost={setSelectedPost} />
</Suspense>
const PostItem = ({ data, setSelectedPost }: Props) => {
return (
<>
{data?.map((post) => (
<li
key={post.id}
className="post-title"
onClick={() => setSelectedPost(post)}
style={{ cursor: 'pointer' }}
>
{post.title}
</li>
))}
</>
)
}
export default PostItem
하지만 그래도 여전히 동작하지 않았다. 여기까지 삽질하고 알게된 부분이 useQuery에 suspense키를 true 값으로 전달하는 방법이었다.
그래서 공식문서에 확인해보니 아래와 같이 전달하거나
// Configure for all queries
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
suspense: true
}
}
})
function Root() {
return (
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
)
}
useQuery이 키 값으로 전달하라는 것이었다.
import { useQuery } from '@tanstack/react-query'
// Enable for an individual query
useQuery({ queryKey, queryFn, suspense: true })
그래서 앞으로 Suspense를 계속 여러 곳에서 활용한다고 가정하고 defaultOptions로 전달해보았다. 하지만 다시 아래와 같은 오류가 발생했다.
'{ suspense: true; }' 형식은 'Omit<QueryObserverOptions<unknown, Error, unknown,
unknown, QueryKey, never>, "suspense">' 형식에 할당할 수 없습니다.
개체 리터럴은 알려진 속성만 지정할 수 있으며 'Omit<QueryObserverOptions<unknown, Error,
unknown, unknown, QueryKey, never>, "suspense">' 형식에 'suspense'이(가) 없습니다.
ts(2322)
그래서 결과적으로 해당 부분을 리액트 쿼리 깃허브에 이슈를 찾아보면서 해결해보려고 했지만, 이렇다할 해결방법을 찾지 못했음.. 리액트 쿼리에서는 굳이 isLoading으로 처리해도 될 부분을 Suspense를 활용해서 할 필요가 없다고 판단한 걸까? 그래서 일단 Suspense를 리액트 쿼리의 isLoading을 이용해서 우겨넣어서 구현해봤다.
import { ReactNode } from 'react'
type Props = {
isLoading: boolean
fallback: ReactNode
children: ReactNode
}
const Suspense = ({ isLoading, fallback, children }: Props) => {
return (
<>
{isLoading && fallback}
{children}
</>
)
}
export default Suspense
<Suspense
isLoading={isLoading}
fallback={<Loading message="블로그 데이터 로딩 중입니다!!" />}
>
{data?.map((post) => (
<li
key={post.id}
className="post-title"
onClick={() => setSelectedPost(post)}
style={{ cursor: 'pointer' }}
>
{post.title}
</li>
))}
</Suspense>
그런데 이렇게 사용할 바에 그냥 isLoading을 사용하는 게 나은 거 아닌가라는 생각이 든다.
<ul>
{isLoading && <Loading message="블로그 데이터 로딩 중입니다!!" />}
{data?.map((post) => (
<li
key={post.id}
className="post-title"
onClick={() => setSelectedPost(post)}
style={{ cursor: 'pointer' }}
>
{post.title}
</li>
))}
</ul>
Suspense로 감싸면 뭔가 좀 더 그 구간은 로딩과 관련있는 컴포넌트나 데이터라는 것이 좀 더 눈에 확 들어오는 효과는 있는 것 같다. 그렇다고 그게 장점은 절대 아닌 것 같다.
여기까지 고민했다가, 다시 공식문서를 살펴보는데, v5가 아닌 v4를 보고 있는 게 문제였다;; v5에서는 suspense가 없어졌지만, v4에서는 유지하고 있는 것 같다..