Skip to content

Commit ad268ef

Browse files
authored
docs: improve no async client components page (vercel#69440)
Some good feedback that this page was very barebones. Added more details and options.
1 parent 9720d0a commit ad268ef

File tree

1 file changed

+135
-3
lines changed

1 file changed

+135
-3
lines changed

errors/no-async-client-component.mdx

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,141 @@ title: No async client component
66
77
## Why This Error Occurred
88

9-
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).
9+
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:
10+
11+
```tsx
12+
'use client'
13+
14+
// This will cause an error
15+
async function ClientComponent() {
16+
// ...
17+
}
18+
```
1019

1120
## Possible Ways to Fix It
1221

13-
1. Remove the `async` keyword from the client component function declaration, or
14-
2. Convert the client component to a server component
22+
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.
23+
2. **Remove the `async` keyword**: If you need to keep it as a client component, remove the `async` keyword and handle data fetching differently.
24+
3. **Use React hooks for data fetching**: Utilize hooks like `useEffect` for client-side data fetching, or use third-party libraries.
25+
4. **Leverage the `use` hook with a Context Provider**: Pass promises to child components using context, then resolve them with the `use` hook.
26+
27+
### Recommended: Server-side data fetching
28+
29+
We recommend fetching data on the server. For example:
30+
31+
```tsx filename="app/page.tsx"
32+
export default async function Page() {
33+
let data = await fetch('https://api.vercel.app/blog')
34+
let posts = await data.json()
35+
return (
36+
<ul>
37+
{posts.map((post) => (
38+
<li key={post.id}>{post.title}</li>
39+
))}
40+
</ul>
41+
)
42+
}
43+
```
44+
45+
### Using `use` with Context Provider
46+
47+
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:
48+
49+
First, let's create a separate file for the context provider:
50+
51+
```tsx filename="app/context.tsx"
52+
'use client'
53+
54+
import { createContext, useContext } from 'react'
55+
56+
export const BlogContext = createContext<Promise<any> | null>(null)
57+
58+
export function BlogProvider({
59+
children,
60+
blogPromise,
61+
}: {
62+
children: React.ReactNode
63+
blogPromise: Promise<any>
64+
}) {
65+
return (
66+
<BlogContext.Provider value={blogPromise}>{children}</BlogContext.Provider>
67+
)
68+
}
69+
70+
export function useBlogContext() {
71+
let context = useContext(BlogContext)
72+
if (!context) {
73+
throw new Error('useBlogContext must be used within a BlogProvider')
74+
}
75+
return context
76+
}
77+
```
78+
79+
Now, let's create the Promise in a Server Component and stream it to the client:
80+
81+
```tsx filename="app/page.tsx"
82+
import { BlogProvider } from './context'
83+
84+
export default function Page() {
85+
let blogPromise = fetch('https://api.vercel.app/blog').then((res) =>
86+
res.json()
87+
)
88+
89+
return (
90+
<BlogProvider blogPromise={blogPromise}>
91+
<BlogPosts />
92+
</BlogProvider>
93+
)
94+
}
95+
```
96+
97+
Here is the blog posts component:
98+
99+
```tsx filename="app/blog-posts.tsx"
100+
'use client'
101+
102+
import { use } from 'react'
103+
import { useBlogContext } from './context'
104+
105+
export function BlogPosts() {
106+
let blogPromise = useBlogContext()
107+
let posts = use(blogPromise)
108+
109+
return <div>{posts.length} blog posts</div>
110+
}
111+
```
112+
113+
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.
114+
115+
### Client-side data fetching
116+
117+
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.
118+
119+
```tsx filename="app/page.tsx"
120+
'use client'
121+
122+
import { useState, useEffect } from 'react'
123+
124+
export function Posts() {
125+
let [posts, setPosts] = useState(null)
126+
127+
useEffect(() => {
128+
async function fetchPosts() {
129+
let res = await fetch('https://api.vercel.app/blog')
130+
let data = await res.json()
131+
setPosts(data)
132+
}
133+
fetchPosts()
134+
}, [])
135+
136+
if (!posts) return <div>Loading...</div>
137+
138+
return (
139+
<ul>
140+
{posts.map((post) => (
141+
<li key={post.id}>{post.title}</li>
142+
))}
143+
</ul>
144+
)
145+
}
146+
```

0 commit comments

Comments
 (0)