Minified React error #426
Published: 2024-01-27 17:10:49


![426](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q4fv8fc8h5gitz4hy5d1.png)


In this blog post, I would like to address an issue I encountered in this week. This is the first time I met this kind of problem using React and first time know the `startTransition` of React.

## How it comes

This error related to the [`ReactMarkdown`](https://github.com/remarkjs/react-markdown) component and the [`useDisclosure`](https://chakra-ui.com/docs/hooks/use-disclosure) hook in the [`@chakra-ui/react`](https://chakra-ui.com/).

```js
<ReactMarkdown children={children}>
 components={{
        code({children, ...props }) {
 // ...
   return (
    // some rendering based on the children, such as
    // <code>{children}</code>
    // <SyntaxHighlighter>{String(children)}SyntaxHighlighter>
    // <Preview children={Array.isArray(children) ? children : [children]} ></Preview>
    )
  }
</ReactMarkdown>
```

```ts
 const { isOpen, onToggle } = useDisclosure();
```

Here is the original workflow:

a button is toggled -> useDisclosure catch the changes -> ReactMarkdown's updates rendering (based on the `isOpen` status)

```ts
<Button onClick={() => onToggle()}>
    {isOpen ? "Show Less" : "Show More..."}
</Button>

<ReactMarkdown>
    {isOpen ? veryLongText : shortText }
</ReactMarkdown>
```

## Error occurs

Now when we have a very very long string `veryLongText`, the update will gives you the [error](https://react.dev/errors/426?invariant=426): 

> 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 indicates that a component has paused or stopped functioning while it was trying to handle synchronous input. Synchronous input refers to actions or events that are processed immediately, without waiting for other tasks to complete.

It suggests that we can wrap the updates or changes that cause the suspension of the component with a function called `startTransition`

## How to solve

Here is the updated to solve the error by using `startTransition` 


```ts

  const { isOpen, onToggle: originalOnToggle } = useDisclosure();

  const onToggle = () => {
    startTransition(() => {
      originalOnToggle();
    });
  };

// no changes to Button and ReactMarkdown code
<Button onClick={() => onToggle()}>
    {isOpen ? "Show Less" : "Show More..."}
</Button>

<ReactMarkdown>
    {isOpen ? veryLongText : shortText }
</ReactMarkdown>
```

We use the `startTransition` function telling React to optimize and batch updates. This means that when the button is clicked, and the `onToggle` function is called, the state update triggered by `originalOnToggle()` is treated as a low-priority update. Here is the full [`diff`](https://patch-diff.githubusercontent.com/raw/tarasglek/chatcraft.org/pull/358.diff) if you are interested:

```diff
diff --git a/src/components/Message/MessageBase.tsx b/src/components/Message/MessageBase.tsx
index 25b83d82..f7f27042 100644
--- a/src/components/Message/MessageBase.tsx
+++ b/src/components/Message/MessageBase.tsx
@@ -1,5 +1,6 @@
 import {
   memo,
+  startTransition,
   useCallback,
   useState,
   useEffect,
@@ -33,6 +34,7 @@ import {
   useClipboard,
   Kbd,
   Spacer,
+  useDisclosure,
 } from "@chakra-ui/react";
 import ResizeTextarea from "react-textarea-autosize";
 import { TbDots, TbTrash } from "react-icons/tb";
@@ -111,6 +113,17 @@ function MessageBase({
   const isNarrowScreen = useMobileBreakpoint();
   const messageForm = useRef<HTMLFormElement>(null);
   const meta = useMemo(getMetaKey, []);
+  const { isOpen, onToggle: originalOnToggle } = useDisclosure();
+  const isLongMessage = text.length > 5000;
+  const displaySummaryText = !isOpen && (summaryText || isLongMessage);
+
+  // Wrap the onToggle function with startTransition, state update should be deferred due to long message
+  // https://reactjs.org/docs/error-decoder.html?invariant=426
+  const onToggle = () => {
+    startTransition(() => {
+      originalOnToggle();
+    });
+  };
 
   useEffect(() => {
     if (settings.countTokens) {
@@ -334,6 +347,14 @@ function MessageBase({
         <CardBody p={0}>
           <Flex direction="column" gap={3}>
             <Box maxWidth="100%" minH="2em" overflow="hidden" px={6} pb={2}>
+              {
+                // only display the button before message if the message is too long
+                isLongMessage ? (
+                  <Button size="sm" variant="ghost" onClick={() => onToggle()}>
+                    {isOpen ? "Show Less" : "Show More..."}
+                  </Button>
+                ) : undefined
+              }
               {editing ? (
                 // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
                 <form onSubmit={handleSubmit} ref={messageForm} onKeyDown={handleKeyDown}>
@@ -381,8 +402,12 @@ function MessageBase({
                   </VStack>
                 </form>
               ) : (
-                <Markdown previewCode={!hidePreviews} isLoading={isLoading} onPrompt={onPrompt}>
-                  {summaryText || text}
+                <Markdown
+                  previewCode={!hidePreviews && !displaySummaryText}
+                  isLoading={isLoading}
+                  onPrompt={onPrompt}
+                >
+                  {displaySummaryText ? summaryText || text.slice(0, 250).trim() : text}
                 </Markdown>
               )}
             </Box>
```

The `startTransition` function is specifically designed to work with the React Concurrent Mode (or just Concurrent Mode), which is an experimental set of features in React for improving the performance and user experience of complex user interfaces. I'm very glad to know this feature. Hope it helps. See you next post!