react icon indicating copy to clipboard operation
react copied to clipboard

[React 19] `use()` promise from state causes "async/await is not yet supported in Client Components" error

Open tom-sherman opened this issue 1 year ago • 1 comments

Summary

https://stackblitz.com/edit/vitejs-vite-kbccdh?file=src%2FApp.tsx

import { use, useState } from 'react';

export default function App() {
  const [p] = useState(() => new Promise((res) => setTimeout(res, 500)));

  use(p);
  return 'hello!';
}

Note: I do not have a parent Suspense boundary.

Actual behaviour

An error is thrown

Error: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding 'use client' to a module that was originally written for the server.

Expected behaviour

I think this should render "hello!". If I lift the promise out of the state initialiser into the module, there is no error - React holds from rendering the app until the promise resolves.

// This works fine, even without a parent <Suspense>
const p = new Promise((res) => setTimeout(res, 500));
export default function App() {
  use(p);
  return 'hello!';
}

If it's expected to throw an error in this scenario, then I think the error message should be improved. It should log the error message about the component suspended without a parent suspense boundary. I do think this is inconsistent though, and in original example should just work.

tom-sherman avatar Aug 15 '24 10:08 tom-sherman

Not seeing the thrown error in stackblitz nor Codesandbox: https://codesandbox.io/p/sandbox/react-use-without-suspense-boundary-mpnzw3

It never commits though. Adding a Suspense boundary fixes it.

eps1lon avatar Aug 16 '24 16:08 eps1lon

@eps1lon that's super weird, same here. Is it expected for it to never commit? I guess that'd be a pretty weird setup to not have a single suspense boundary in your entire app, but would be good to add a warning.

tom-sherman avatar Sep 19 '24 09:09 tom-sherman

The issue is also that the Promise is created during render in the state intializer. When we suspend, state is discarded so we end up re-creating the Promise when the prior one resolves. Once you hoist the Promise creation outside of the component, it commits just fine: https://codesandbox.io/p/sandbox/react-use-without-suspense-boundary-forked-799ph3?file=%2Fsrc%2Findex.js%3A5%2C32&workspaceId=ae5ed9d5-7ce3-4b4a-a32e-93bfc31fc065

Leaving this open since the error message is misleading.

eps1lon avatar Sep 19 '24 18:09 eps1lon