From ad268ef318e4f47b19e4a098404a8112ca39acb6 Mon Sep 17 00:00:00 2001 From: Lee Robinson Date: Thu, 29 Aug 2024 09:23:43 -0500 Subject: [PATCH] docs: improve no async client components page (#69440) Some good feedback that this page was very barebones. Added more details and options. --- errors/no-async-client-component.mdx | 138 ++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 3 deletions(-) diff --git a/errors/no-async-client-component.mdx b/errors/no-async-client-component.mdx index 399f375392e0a..3318fc179f495 100644 --- a/errors/no-async-client-component.mdx +++ b/errors/no-async-client-component.mdx @@ -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 ( + + ) +} +``` + +### 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 | null>(null) + +export function BlogProvider({ + children, + blogPromise, +}: { + children: React.ReactNode + blogPromise: Promise +}) { + return ( + {children} + ) +} + +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 ( + + + + ) +} +``` + +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
{posts.length} blog posts
+} +``` + +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
Loading...
+ + return ( +
    + {posts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +```