Skip to content

Commit

Permalink
docs: improve no async client components page (#69440)
Browse files Browse the repository at this point in the history
Some good feedback that this page was very barebones. Added more details
and options.
  • Loading branch information
leerob authored Aug 29, 2024
1 parent 9720d0a commit ad268ef
Showing 1 changed file with 135 additions and 3 deletions.
138 changes: 135 additions & 3 deletions errors/no-async-client-component.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,141 @@ title: No async client component
## Why This Error Occurred

As per the [React Server Component RFC on promise support](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md), [client components cannot be async functions](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#why-cant-client-components-be-async-functions).
The error occurs when you try to define a client component as an async function. React client components [do not support](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#why-cant-client-components-be-async-functions) async functions. For example:

```tsx
'use client'

// This will cause an error
async function ClientComponent() {
// ...
}
```

## Possible Ways to Fix It

1. Remove the `async` keyword from the client component function declaration, or
2. Convert the client component to a server component
1. **Convert to a Server Component**: If possible, convert your client component to a server component. This allows you to use `async`/`await` directly in your component.
2. **Remove the `async` keyword**: If you need to keep it as a client component, remove the `async` keyword and handle data fetching differently.
3. **Use React hooks for data fetching**: Utilize hooks like `useEffect` for client-side data fetching, or use third-party libraries.
4. **Leverage the `use` hook with a Context Provider**: Pass promises to child components using context, then resolve them with the `use` hook.

### Recommended: Server-side data fetching

We recommend fetching data on the server. For example:

```tsx filename="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>
)
}
```

### Using `use` with Context Provider

Another pattern to explore is using the React `use` hook with a Context Provider. This allows you to pass Promises to child components and resolve them using the `use` hook . Here's an example:

First, let's create a separate file for the context provider:

```tsx filename="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
}
```

Now, let's create the Promise in a Server Component and stream it to the client:

```tsx filename="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>
)
}
```

Here is the blog posts component:

```tsx filename="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>
}
```

This pattern allows you to start data fetching early and pass the Promise down to child components, which can then use the `use` hook to access the data when it's ready.

### Client-side data fetching

In scenarios where client fetching is needed, you can call `fetch` in `useEffect` (not recommended), or lean on popular React libraries in the community (such as [SWR](https://swr.vercel.app/) or [React Query](https://tanstack.com/query/latest)) for client fetching.

```tsx filename="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>
)
}
```

0 comments on commit ad268ef

Please sign in to comment.