facebook/react · error · Error

600

600

Error message

A rejected Promise was passed to React without a `reason` property. React threw a generic error from where the Promise was used to assist in identifying the problematic Promise. Make sure that instrumented Promises correctly set the `reason` property when setting `status` to `'rejected'`.

What it means

React inspects thenables passed to use()/rendered server-side for a status property set by instrumentation libraries. When status === 'rejected', it reads .reason to rethrow the original error; this guard fires when 'reason' is entirely absent — a broken instrumentation that set status without reason. React refuses to throw undefined (which would lose the stack), so it throws this explicit error at the use site to help locate the faulty promise.

Source

Thrown at packages/react-server/src/ReactFlightThenable.js:96

  switch (thenable.status) {
    case 'fulfilled': {
      // This could be a bad instrumentation that doesn't set .value.
      // We're not type-checking since this is a hot path where you can
      // track down easily when something becomes `undefined` unexpectedly.
      const fulfilledValue: T = thenable.value;
      return fulfilledValue;
    }
    case 'rejected': {
      const rejectedError = thenable.reason;

      // Rejected Promises are rarer so we're doing an extra type-check in
      // case of a bad instrumentation that doesn't set .reason
      // If we end up throwing `undefined` it becomes hard to track down
      // where that throw originated because no callstack would exist.
      // React would still have a Component stack but that could only be used
      // as an approximation.
      if (rejectedError === undefined && !('reason' in thenable)) {
        throw new Error(
          'A rejected Promise was passed to React without a `reason` property. ' +
            'React threw a generic error from where the Promise was used to assist in identifying the problematic Promise. ' +
            "Make sure that instrumented Promises correctly set the `reason` property when setting `status` to `'rejected'`.",
        );
      }

      throw rejectedError;
    }
    default: {
      if (typeof thenable.status === 'string') {
        // Only instrument the thenable if the status if not defined. If
        // it's defined, but an unknown value, assume it's been instrumented by
        // some custom userspace implementation. We treat it as "pending".
        // Attach a dummy listener, to ensure that any lazy initialization can
        // happen. Flight lazily parses JSON when the value is actually awaited.
        thenable.then(noop, noop);
      } else {
        const pendingThenable: PendingThenable<T> = thenable as any;

View on GitHub (pinned to eafeac097b)

Solutions

  1. Fix the instrumentation: always set status and reason (promise.reason = error) together, or set neither and let React attach its own handlers.
  2. Upgrade the third-party library doing the instrumentation if it is not yours.
  3. Audit every place that assigns .status on a thenable and pair it with .value/.reason.

Example fix

// before — custom cache instrumentation
function instrument(p) {
  p.then(v => { p.status = 'fulfilled'; p.value = v; },
         e => { p.status = 'rejected'; });
  return p;
}

// after
function instrument(p) {
  p.then(v => { p.status = 'fulfilled'; p.value = v; },
         e => { p.status = 'rejected'; p.reason = e; });
  return p;
}
Defensive patterns

Strategy: type-guard

Validate before calling

export function isSafelyInstrumented(p: unknown): boolean {
  if (p == null || typeof (p as any).then !== 'function') return true;
  const t = p as {status?: unknown};
  return !(t.status === 'rejected' && !('reason' in (t as object)));
}
// before use()/rendering:
if (!isSafelyInstrumented(p)) (p as any).reason = new Error('unknown: missing reason');

Type guard

export function hasReasonWhenRejected(p: unknown): p is Promise<unknown> {
  return typeof (p as any)?.then === 'function'
    && !((p as any).status === 'rejected' && !('reason' in (p as object)));
}

Prevention

When it happens

Trigger: use(p) or rendering a component that returns a thenable where p.status = 'rejected' was assigned without setting p.reason; custom Suspense caches that eagerly mark promises rejected during construction; libraries copying React's instrumentation pattern halfway.

Common situations: Hand-rolled memoization/suspense caches; data-fetch wrappers that tag promises for dedupe; framework code pre-marking aborted requests as rejected without attaching the abort error.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/89246897818383b2. Report an issue: GitHub.