コンテンツにスキップ
ドキュメントエラー非同期クライアントコンポーネントは使用できません

非同期クライアントコンポーネントは使用できません

クライアントコンポーネントは非同期関数にできません。

このエラーが発生した理由

このエラーは、クライアントコンポーネントを非同期関数として定義しようとすると発生します。Reactのクライアントコンポーネントは非同期関数をサポートしていません。例:

'use client'
 
// This will cause an error
async function ClientComponent() {
  // ...
}

考えられる解決策

  1. サーバーコンポーネントに変換する: 可能であれば、クライアントコンポーネントをサーバーコンポーネントに変換してください。これにより、コンポーネントでasync/awaitを直接使用できるようになります。
  2. asyncキーワードを削除する: クライアントコンポーネントとして維持する必要がある場合は、asyncキーワードを削除し、データの取得方法を別の方法で処理してください。
  3. データ取得にReactフックを使用する: クライアントサイドでのデータ取得にはuseEffectのようなフックを利用するか、サードパーティライブラリを使用してください。
  4. Context Providerとuseフックを活用する: プロミスをコンテキストを使用して子コンポーネントに渡し、useフックで解決します。

サーバーでのデータ取得を推奨します。例:

app/page.tsx
export default async function Page() {
  const data = await fetch('https://api.vercel.app/blog')
  const posts = await data.json()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Context Providerとuseフックの使用

検討すべきもう一つのパターンは、ReactのuseフックをContext Providerと共に使用することです。これにより、Promisesを子コンポーネントに渡し、useフックを使用して解決することができます。例:

まず、コンテキストプロバイダー用のファイルを別に作成しましょう

app/context.tsx
'use client'
 
import { createContext, useContext } from 'react'
 
export const BlogContext = createContext<Promise<any> | null>(null)
 
export function BlogProvider({
  children,
  blogPromise,
}: {
  children: React.ReactNode
  blogPromise: Promise<any>
}) {
  return (
    <BlogContext.Provider value={blogPromise}>{children}</BlogContext.Provider>
  )
}
 
export function useBlogContext() {
  const context = useContext(BlogContext)
  if (!context) {
    throw new Error('useBlogContext must be used within a BlogProvider')
  }
  return context
}

次に、サーバーコンポーネントでPromiseを作成し、それをクライアントにストリーミングしましょう

app/page.tsx
import { BlogProvider } from './context'
 
export default function Page() {
  const blogPromise = fetch('https://api.vercel.app/blog').then((res) =>
    res.json()
  )
 
  return (
    <BlogProvider blogPromise={blogPromise}>
      <BlogPosts />
    </BlogProvider>
  )
}

ブログ投稿コンポーネントはこちらです

app/blog-posts.tsx
'use client'
 
import { use } from 'react'
import { useBlogContext } from './context'
 
export function BlogPosts() {
  const blogPromise = useBlogContext()
  const posts = use(blogPromise)
 
  return <div>{posts.length} blog posts</div>
}

このパターンにより、データの取得を早期に開始し、Promiseを子コンポーネントに渡すことができます。子コンポーネントは、データが準備できたときにuseフックを使用してデータにアクセスできます。

クライアントサイドでのデータ取得

クライアントサイドでのフェッチが必要なシナリオでは、useEffect内でfetchを呼び出す(非推奨)か、クライアントサイドフェッチのためにコミュニティで人気のReactライブラリ(SWRReact Queryなど)を利用することができます。

app/page.tsx
'use client'
 
import { useState, useEffect } from 'react'
 
export function Posts() {
  const [posts, setPosts] = useState(null)
 
  useEffect(() => {
    async function fetchPosts() {
      const res = await fetch('https://api.vercel.app/blog')
      const data = await res.json()
      setPosts(data)
    }
    fetchPosts()
  }, [])
 
  if (!posts) return <div>Loading...</div>
 
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}