コンテンツにスキップ
ドキュメントエラー非同期クライアントコンポーネント不可

非同期クライアントコンポーネント不可

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

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

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

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

修正方法

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

サーバーでのデータフェッチをお勧めします。例えば

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

Context Provider で `use` を使用する

検討すべきもう一つのパターンは、Reactの `use` hook を Context Provider と共に使用することです。これにより、Promise を子コンポーネントに渡し、`use` hook を使用して準備ができたときにデータにアクセスできます。次に例を示します

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

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() {
  let 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() {
  let 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() {
  let blogPromise = useBlogContext()
  let posts = use(blogPromise)
 
  return <div>{posts.length} blog posts</div>
}

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

クライアントサイドデータフェッチ

クライアント側でのデータ取得が必要なシナリオでは、useEffect 内で fetch を呼び出すことができます(推奨されません)。または、クライアント側でのデータ取得のために、コミュニティで人気のある React ライブラリ(SWRReact Queryなど)を活用することもできます。

app/page.tsx
'use client'
 
import { useState, useEffect } from 'react'
 
export function Posts() {
  let [posts, setPosts] = useState(null)
 
  useEffect(() => {
    async function fetchPosts() {
      let res = await fetch('https://api.vercel.app/blog')
      let 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>
  )
}