👀 Suspensive v2에서의 변경을 확인하세요. 더보기 →
문서보기@suspensive/react-query<SuspenseQuery/>

SuspenseQuery

같은 depth에서 Suspense를 발생시키는 것이 무엇인지 명확하게 표현하기 위해 이 컴포넌트들을 제공합니다.

  1. data-fetching만을 위한 UserInfo, PostList와 같은 depth를 제거하기 때문에 생긴 prop-drilling도 제거됩니다.
  2. Suspense, ErrorBoundary의 범위 변경도 간단해집니다. query의 병렬처리도 더 쉽습니다.
  3. Page 컴포넌트 내에서 data-fetching을 모두 관장하기 때문에 내부의 컴포넌트는 presentational하므로 컴포넌트를 분리하기 쉽습니다.
import { SuspenseQuery } from '@suspensive/react-query'
import { Suspense, ErrorBoundary } from '@suspensive/react'
import { PostListItem, UserProfile } from '~/components'
 
const PostsPage = ({ userId }) => (
  <ErrorBoundary fallback={({ error }) => <>{error.message}</>}>
    <Suspense fallback={'loading...'}>
      <SuspenseQuery {...userQueryOptions(userId)}>
        {({ data: user }) => <UserProfile key={user.id} {...user} />}
      </SuspenseQuery>
      <SuspenseQuery
        {...postsQueryOptions(userId)}
        select={(posts) => posts.filter(({ isPublic }) => isPublic)}
      >
        {({ data: posts }) =>
          posts.map((post) => <PostListItem key={post.id} {...post} />)
        }
      </SuspenseQuery>
    </Suspense>
  </ErrorBoundary>
)
import { useState } from 'react'
import { ErrorBoundary, Suspense } from '@suspensive/react'
import { SuspenseQuery } from '@suspensive/react-query'
import { PostListItem } from './PostListItem'
import { UserProfile } from './UserProfile'
import { postsQueryOptions, userQueryOptions } from './queries'

export const Example = () => {
  const [userId, setUserId] = useState(1)
  const [showAllPosts, setShowAllPosts] = useState(false)

  return (
    <ErrorBoundary fallback={({ error }) => <>{error.message}</>}>
      <button onClick={() => setUserId((id) => id - 1)} disabled={userId === 1}>
        Previous User
      </button>
      <button
        onClick={() => setUserId((id) => id + 1)}
        disabled={userId === 10}
      >
        Next User
      </button>
      <Suspense fallback={<div>Loading...</div>}>
        <SuspenseQuery {...userQueryOptions(userId)}>
          {({ data: user }) => <UserProfile key={user.id} {...user} />}
        </SuspenseQuery>

        <label>
          <input
            type="checkbox"
            checked={showAllPosts}
            onChange={() => setShowAllPosts((showAllPosts) => !showAllPosts)}
          />
          Show All Posts
        </label>

        <SuspenseQuery
          {...postsQueryOptions(userId)}
          select={(posts) =>
            posts.filter(({ isPublic }) => showAllPosts || isPublic)
          }
        >
          {({ data: posts }) => (
            <ol>
              {posts.map((post) => (
                <PostListItem key={post.id} {...post} />
              ))}
            </ol>
          )}
        </SuspenseQuery>
      </Suspense>
    </ErrorBoundary>
  )
}

동기: useSuspenseQuery가 명확히 드러나지 않음

기존의 useSuspenseQuery는 훅이기 때문에 부모에 Suspense, ErrorBoundary를 배치하기 위해 UserInfo, PostList와 같은 이름을 가진 컴포넌트를 만들게 합니다. 이것은 UserInfo, PostList 내부에서 던져질 suspense와 error가 있을지 예측하기 어렵게 만듭니다.

// posts/page.tsx
import { Suspense, ErrorBoundary } from '@suspensive/react'
import { UserInfo } from './components/UserInfo'
import { PostList } from './components/PostList'
 
const PostsPage = ({ userId }) => (
  <ErrorBoundary fallback={({ error }) => <>{error.message}</>}>
    <Suspense fallback={'loading...'}>
      <UserInfo userId={userId} />{' '}
      {/* 내부적으로 Suspense를 발생할 지 예상하기 어렵습니다. */}
      <PostList userId={userId} />{' '}
      {/* 내부적으로 Suspense를 발생할 지 예상하기 어렵습니다. */}
    </Suspense>
  </ErrorBoundary>
)
// posts/components/UserInfo.tsx
import { useSuspenseQuery } from '@suspensive/react-query'
import { UserProfile } from '~/components'
 
// 이 컴포넌트를 사용하는 입장에서는 UserInfo라는 이름만으로는 내부적으로 Suspense를 발생시키는 지 예측할 수 없습니다.
const UserInfo = ({ userId }) => {
  // data-fetching만을 위한 이 컴포넌트를 만들어야 합니다.
  const { data: user } = useSuspenseQuery(userQueryOptions(userId))
 
  return <UserProfile {...user} />
}
// posts/components/PostList.tsx
import { useSuspenseQuery } from '@suspensive/react-query'
import { PostListItem } from '~/components'
 
// 이 컴포넌트를 사용하는 입장에서는 PostList라는 이름만으로는 내부적으로 Suspense를 발생시키는 지 예측할 수 없습니다.
const PostList = ({ userId }) => {
  // data-fetching만을 위한 이 컴포넌트를 만들어야 합니다.
  const { data: posts } = useSuspenseQuery({
    ...postsQueryOptions(userId),
    select: (posts) => posts.filter(({ isPublic }) => isPublic),
  })
 
  return (
    <>
      {posts.map((post) => (
        <PostListItem {...post} />
      ))}
    </>
  )
}