Bug: Error message "Uncaught Error: A component suspended while responding to synchronous input." may be misleading

This issue has been created since 2022-11-03.

React version: 18.2.0

Steps To Reproduce

Do anything that triggers the error:

Uncaught Error: A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.

This seems to occur when a synchronous input causes a component to suspend, without a suspense boundary defined.

To create the specific situation where I saw the error:

  1. Set up react-router with two routes, each containing a component that suspends, using a Relay hook for a graphql query. Do not provide a Suspend around either component.
  2. Navigate from route A to route B.
  3. Refresh the page so the Relay cache is cleared.
  4. Use the browser back button to navigate back from B to A, causing the component in A to suspend as it performs the graphql query.

Link to code example:

Can provide if needed, however the issue seems to be that the wording of the error doesn't match the conditions that cause the error to be thrown in React code (see below), so a code example might not be necessary?

The current behavior

The following error message is displayed:

Uncaught Error: A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.

stacktrace

This implies that the only way to fix the error is to use "startTransition". While this did fix my error, looking at the code in question it seems that the more obvious problem is that there was no suspense boundary around the component in question. This problem is also much easier to fix - in my case could add transitions easily for some cases (around navigateTo calls to react-router), but catching every way of navigating and adding a transition seems to be quite difficult. I added a Suspense around the suspending component, and this resolved the error. Using "startTransition" does have extra advantages of allowing the old state of the suspended component to display instead of a fallback, but this seems like it should be more of an "information" notice than an error, so I'm assuming it's only the lack of both a suspense boundary and a synchronous input that is intended to be flagged as an error?

Looking at the React code seems to confirm this:

    var suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);

    if (suspenseBoundary !== null) {
      suspenseBoundary.flags &= ~ForceClientRender;
      markSuspenseBoundaryShouldCapture(suspenseBoundary, returnFiber, sourceFiber, root, rootRenderLanes); // We only attach ping listeners in concurrent mode. Legacy Suspense always
      // commits fallbacks synchronously, so there are no pings.

      if (suspenseBoundary.mode & ConcurrentMode) {
        attachPingListener(root, wakeable, rootRenderLanes);
      }

      attachRetryListener(suspenseBoundary, root, wakeable);
      return;
    } else {
      // No boundary was found. Unless this is a sync update, this is OK.
      // We can suspend and wait for more data to arrive.
      if (!includesSyncLane(rootRenderLanes)) {
        // This is not a sync update. Suspend. Since we're not activating a
        // Suspense boundary, this will unwind all the way to the root without
        // performing a second pass to render a fallback. (This is arguably how
        // refresh transitions should work, too, since we're not going to commit
        // the fallbacks anyway.)
        //
        // This case also applies to initial hydration.
        attachPingListener(root, wakeable, rootRenderLanes);
        renderDidSuspendDelayIfPossible();
        return;
      } // This is a sync/discrete update. We treat this case like an error
      // because discrete renders are expected to produce a complete tree
      // synchronously to maintain consistency with external state.


      var uncaughtSuspenseError = new Error('A component suspended while responding to synchronous input. This ' + 'will cause the UI to be replaced with a loading indicator. To ' + 'fix, updates that suspend should be wrapped ' + 'with startTransition.'); // If we're outside a transition, fall through to the regular error path.
      // The error will be caught by the nearest suspense boundary.

      value = uncaughtSuspenseError;
    }
  } else {
    // This is a regular error, not a Suspense wakeable.

I could be reading this wrongly, but it looks like the error I'm seeing is only raised when there is no boundary found, and we have a sync update. If there is a suspense boundary, it looks like the error doesn't apply, and there is a comment specifically covering that if there is no boundary, only sync updates are an error ("Unless this is a sync update, this is OK.").

In addition, the component does not seem to re-render when data is received, the output remains empty, presumably because this is an unrecoverable error?

The expected behavior

An error is displayed which covers both options for resolving, e.g.:

Uncaught Error: A component suspended while responding to synchronous input, and no suspense boundary was provided. To fix, provide a suspense boundary, or ensure that updates that suspend are wrapped with startTransition, or both. Note that providing a suspense boundary but omitting startTransition will cause the UI to be replaced with a loading indicator.

This is a little wordy, so maybe it should be a link to a documentation page instead?

eps1lon wrote this answer on 2022-11-04

Can provide if needed, however the issue seems to be that the wording of the error doesn't match the conditions that cause the error to be thrown in React code (see below), so a code example might not be necessary?

Please do provide a minimal example where the error would be misleading.

bmwebster wrote this answer on 2022-11-28

Sorry for the delay - just back from holiday!

I've put together a very simple replication as a sandbox here:

https://codesandbox.io/s/charming-jang-y5evsf

The core of this is just having a component suspend without a suspense boundary:

import { Suspense } from "react";

const SuspenseTrigger = () => {
  throw new Promise(() => {});
};

export default function App() {
  return (
    <div className="App">
      <h1>Suspense example</h1>
      {/* <Suspense fallback={<div>loading...</div>}> */}
      <SuspenseTrigger />
      {/* </Suspense> */}
    </div>
  );
}

The sandbox seems to give slightly odd behaviour on reloading, so you'll need to do these steps in order to show the issue:

  1. Open the sandbox - this starts with no suspense happening (the suspending component is commented out), and should just display "Suspense Example"

    Screenshot 2022-11-28 at 13 03 56
  2. In App.js, uncomment the <SuspenseTrigger /> line. This suspends on render but there is no suspense boundary so we see the misleading error. This replicates what I'm seeing when Relay suspends to wait for a query result. The error should be as below:

    Screenshot 2022-11-28 at 13 04 29
  3. The simplest fix is to add a suspense boundary, rather than using startTransition as described in the error message. To check this, uncomment the remaining two commented lines in App.js. This gives a fallback component as expected, and resolves the error without requiring startTransition:

    Screenshot 2022-11-28 at 13 13 44

Adding startTransition in cases where it makes sense seems to be a nice-to-have addition after a suspense boundary is in place, in that it will delay the transition until the suspending component is ready. However a) this doesn't seem to be the primary problem - that's the lack of suspense boundary, b) the suspense boundary is much easier to add and more general, and c) it seems like it may not always be possible to use a transition. For example where the problem occurs on first load as in the minimal example, or in my react-router case where we can benefit from using startTransition in some places, but it's difficult to make sure any possible navigation route is done as a transition, and missing any out will lead to an error unless we also add a suspense boundary.

So my feeling is that if the error message also suggested adding a suspense boundary, it would reinforce good practice, and in my case would also have got me much more quickly to the real issue of a misplaced <Suspense> component. There may well be other approaches I'm missing, in which case they could be added to the message or in a referenced docs page?

More Details About Repo
Owner Name facebook
Repo Name react
Full Name facebook/react
Language JavaScript
Created Date 2013-05-24
Updated Date 2022-12-01
Star Count 198482
Watcher Count 6646
Fork Count 41196
Issue Count 1106

YOU MAY BE INTERESTED

Issue Title Created Date Comment Count Updated Date
No moudle named PyQt4 2 2021-06-11 2022-09-27
Media Recording: missing audio when using blurring 0 2022-09-14 2022-10-06
Why not have a profile that remains synced with the normal browser? 3 2021-09-25 2022-10-02
Setting "Page size" does not work 1 2020-11-11 2022-11-07
WSA Crashes after trying to merge the last update 8 2021-12-30 2022-07-30
Get Releases.... 2 2021-07-15 2021-12-22
release里面的文件下载很慢,怎么可以加速下载? 2 2022-02-10 2022-11-06
Can not render reducer state that is stored as a map properly 0 2021-03-04 2022-11-19
Potential issue if an event handler disconnects itself during event handling 2 2021-02-22 2022-10-13
cannot load Kubernetes client config (.kube: The handle is invalid) after adding v18 1 2022-02-03 2022-11-09
Report slow string convertions using fmt 9 2021-06-15 2022-09-21
Wrong formatting of numbers 0 2021-04-06 2022-11-13
Clause cannot be reached on a complex list pattern 2 2022-06-08 2022-11-20
🐛 Semantics: Incorrect redeclaration error for parameters of arrow function expressions 5 2022-07-29 2022-09-22
ceph-spec: CR has changed for "ceph-filesystem" - MetadaServer ActiveCount changed in a continous loop 8 2022-09-20 2022-11-25
API v2 and internal graphs don't match RRD Tool output 0 2021-11-23 2022-11-12
UI - Change StoreType enums to correct values accepted by the API 2 2021-04-27 2022-11-04
Add Kamoji 0 2021-07-26 2022-01-16
Run cron as non root user 9 2018-02-09 2022-11-23
how to set app.config? 1 2021-05-11 2022-10-17
Feature Request: Specify user for `trellis ssh` 5 2022-05-02 2022-11-29
[Bug]: UI issue on Add Query for Authenticated API 1 2022-04-13 2022-10-18
SQLSTATE[HY000] [14] unable to open database file 3 2021-04-01 2022-11-29
Missing run-time dependencies in packages `ebtables`, `libassuan` and `rpm-ostree` 1 2022-03-18 2022-11-23
ODAS with Kinect DK 1 2020-03-16 2022-01-22
Some ANSI sequences are treated strangely on Windows 3 2021-05-10 2022-11-23
[SUGGESTION] Add, PROFESSION, REPUTATION, MONEY 1 2020-07-16 2022-09-27
Asset: Show meaningful error instead of technical when booked and total no of depreciation is same 0 2022-01-03 2022-09-27
NVIDIA GPU subsystem ID lost on GC6 exit 1 2021-08-26 2022-11-24
Google Ad Manager not working 10 2020-11-09 2022-11-01
Little kinda sorta stupid question 6 2022-02-21 2022-10-28
Problem when loading page with v-for 0 2021-12-14 2022-11-22
Need separate Debug & Release build output directories at the very least (IEP-791) 3 2022-11-02 2022-11-23
freertos/xtensa_api.h no such file or directory (IEP-766) 4 2022-09-25 2022-11-23
[19.03] Bump Version to 19.03.7 2 2020-03-03 2022-11-27
Component name casing (`CoolLightbox` instead of `CoolLightBox `) 0 2022-07-11 2022-11-10
[rfc] Notification API Refactor 9 2022-06-08 2022-10-02
Crash on intent that requires call permission 0 2021-05-21 2021-12-07
warning build: Object file (/xxxxx/ZipArchive.o) was built for newer iOS version (15.5) than being linked (13.0) 0 2022-07-13 2022-11-11
Support for deleting old functions 1 2020-11-21 2022-11-15
Add entry/exit animation to `Popup` 1 2021-11-18 2022-11-20
Lose order of the completions problem 0 2021-07-10 2022-11-13
new_group_delay doesn't override new_host_delay 1 2022-08-18 2022-10-30
all devices are added except my Mac mini 5 2022-04-25 2022-10-08
`random.split` should support splitting starting at a particular index, such as `step` number 7 2021-08-30 2022-11-08
More libraries need to be moved to manual-link. 8 2019-02-28 2022-11-05
Bump mocha from 5.2.0 to 8.3.0 1 2021-02-12 2022-11-18
Migration from 0.61.2 -> 0.70.1 Error in AppDelegate.mm Cannot initialize a parameter of type 'id<RCTBridgeDelegate>' with an lvalue of type 'AppDelegate *const __strong' 1 2022-09-19 2022-11-17
Add `Learn more` option to `Brave Talk` NTP card for consistency with other cards 0 2022-05-08 2022-09-11
[wasm] V8/Linux `RuntimeError: memory access out of bounds` for `System.Text.Json.Tests` 2 2022-02-03 2022-09-04