Minified React error #426

mingming-ma

Mingming Ma

Posted on January 27, 2024

Minified React error #426

426

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 component and the useDisclosure hook in the @chakra-ui/react.

<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>
Enter fullscreen mode Exit fullscreen mode
 const { isOpen, onToggle } = useDisclosure();
Enter fullscreen mode Exit fullscreen mode

Here is the original workflow:

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

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

<ReactMarkdown>
    {isOpen ? veryLongText : shortText }
</ReactMarkdown>
Enter fullscreen mode Exit fullscreen mode

Error occurs

Now when we have a very very long string veryLongText, the update will gives you the 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 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


  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>
Enter fullscreen mode Exit fullscreen mode

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 if you are interested:

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>
Enter fullscreen mode Exit fullscreen mode

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!

💖 💪 🙅 🚩
mingming-ma
Mingming Ma

Posted on January 27, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related