vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.92k stars 26.98k forks source link

`use` doesn't work in `async` Server Component #44778

Open amannn opened 1 year ago

amannn commented 1 year ago

Verify canary release

Provide environment information

    Operating System:
      Platform: darwin
      Arch: x64
      Version: Darwin Kernel Version 22.2.0: Fri Nov 11 02:08:47 PST 2022; root:xnu-8792.61.2~4/RELEASE_X86_64
    Binaries:
      Node: 16.16.0
      npm: 8.11.0
      Yarn: 1.22.18
      pnpm: 7.1.7
    Relevant packages:
      next: 13.1.2-canary.4
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue

https://github.com/amannn/nextjs-bug-use-server

To Reproduce

  1. Clone the repro
  2. Run npm run dev
  3. Access http://localhost:3000

Describe the Bug

If I make use of use in an async component, this throws:

Screenshot 2023-01-11 at 16 56 03

Expected Behavior

I understand that in an async component you'd typically use async/await. However for reusable library code that can be executed either on the server or the client side, it would be very helpful if use can be called in server code as well.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

Fredkiss3 commented 1 year ago

First thing, i don't think async server components work directly with use, since use can already load async functions and use replaces the await directive.

Secondly in your code you return directly the use, which i don't think is a good pattern ? You could do this for ex :

import {use} from 'react';

export default function Index() {
  const status = use(
    fetch('https://dog.ceo/api/breeds/image/random').then(
      (response) => response.status
    )
  );
 return status;
}

And if you have multiple async operations, just call use multiple times :

import {use} from 'react';

export default function Index() {
  const status = use(
    fetch('https://dog.ceo/api/breeds/image/random').then(
      (response) => response.status
    )
  );

 const result2 = use(async () => { /*... do something async here */ });

 return status;
}
amannn commented 1 year ago

Hey, thanks for your reply!

First thing, i don't think async server components work directly with use, since use can already load async functions and use replaces the await directive.

As mentioned in my issue I understand that in an async component you'd typically use async/await. However for reusable library code that can be executed either on the server or the client side, it would be very helpful if use can be called in server code as well.

Secondly in your code you return directly the use, which i don't think is a good pattern ?

That doesn't make a difference.

dbk91 commented 1 year ago

I believe what @Fredkiss3 was pointing out is that use and async/await are simply paradigms to resolve promises in clients and servers respectively.

In your case, your shared library code would actually be the fetch call itself, exclusive of the keywords used to handle promises. To make this example clearer, extract the fetch call to a separate invokable function which returns a promise.

// api.js
export function getApiStatus() {
  return fetch('https://dog.ceo/api/breeds/image/random').then(
    (response) => response.status
  )
} 

Now you can easily share that code between the client and server using a pattern similar to the examples below.

// Client component
'use client';

import { use } from 'react';
import { getApiStatus } from 'api.js'

function MyClientComponent() {
  const responseStatus = use(getApiStatus())

  return <div>API Status: {responseStatus}</div>
}
// Server component

import { getApiStatus } from 'api.js'

async function MyServerComponent() {
  const responseStatus = await getApiStatus()

  return <div>API Status: {responseStatus}</div>
}

I hope this makes the intent of these APIs clearer.