facebook/react

[React 19] useTranslations Hook Causes "Expected a Suspended Thenable" Error in Async React Components Requiring Client-Side Rendering

bramteunis opened this issue · 4 comments

Bug Report

React version: 19


Description of the Bug

When creating an async React component and calling the useTranslations hook, which requires client-side rendering, React does not provide an error message indicating the issue. Instead, it throws an internal error with the following message:

Internal error: Error: Expected a suspended thenable. This is a bug in React. Please file an issue.
    at getSuspendedThenable ...
    at retryTask ...
    at performWork ...

It appears that React is not correctly handling the combination of useTranslations (a client-side rendering hook) inside an asynchronous component. This leads to React attempting to suspend improperly, resulting in the error.


Steps to Reproduce

  1. Create an async React component.
  2. Use the useTranslations hook inside the component.
  3. Attempt to render the component in a server-side context or without proper client-side rendering setup.
  4. Observe the error described above.

Link to Code Example

Here is a minimal example demonstrating the issue:

import { useTranslations } from 'next-intl';

const AsyncComponent = async () => {
  const t = useTranslations(); // Requires client-side rendering
  return <div>{t('exampleKey')}</div>;
};

export default AsyncComponent;

The Current Behavior

Instead of providing a clear error indicating the need for client-side rendering or proper handling of hooks within async components, React throws the following error:

Internal error: Error: Expected a suspended thenable. This is a bug in React. Please file an issue.
    at getSuspendedThenable ...
    at retryTask ...
    at performWork ...

Additionally, errors such as Invalid state: ReadableStream is already closed and failed to pipe response are observed, leading to significant debugging overhead.


The Expected Behavior

React should:

  1. Provide a clear error message that explains why useTranslations cannot be used in this context (e.g., "useTranslations must be called in a client-side rendering context").
  2. Avoid internal errors such as "Expected a suspended thenable."

This would help developers quickly identify and fix the issue without requiring extensive debugging of React internals.

Hi,

I’ve implemented a change that directly addresses the unclear error message encountered when hooks like useTranslations are improperly used in server-rendering or asynchronous contexts. The updated logic ensures that React throws a descriptive error when encountering an invalid "thenable" or improper usage of client-side-only hooks.

Code Change

Here’s the modification I made to the getSuspendedThenable function:

if (suspendedThenable === null || typeof suspendedThenable.then !== 'function') {
  // Check if the thrown value is a valid thenable (i.e., a Promise-like object).
  // React relies on thenables for managing Suspense during asynchronous rendering.
  // If the thrown value is invalid (null, undefined, or not a thenable),
  // an error is thrown with a clear message explaining the issue.
  //
  // This ensures that async components or hooks, such as `useTranslations`, are
  // only used in client-side rendering contexts, and not during server-side
  // rendering, where they can lead to incorrect behavior and errors.
  throw new Error(
    'Invalid use of async components or hooks like `useTranslations` in server ' +
    'rendering context. Ensure all hooks are used in synchronous client-rendered ' +
    'components.'
  );
}

Outcome

With this change, if an invalid thenable is encountered or a client-side-only hook is used incorrectly during server rendering, developers now receive a clear error message:

"Invalid use of async components or hooks like useTranslations in server rendering context. Ensure all hooks are used in synchronous client-rendered components."

This eliminates the ambiguous "Expected a suspended thenable" error, which was difficult to debug.

Request for Feedback

I’d appreciate feedback on whether this change sufficiently addresses the issue. Should I proceed with further enhancements, such as implementing a general validation mechanism for all client-side-only hooks? Or is this level of error handling adequate for this case?

Looking forward to your thoughts!

Perfect, i do think this error handling suits this use case well enough.