Skip to content

Bug: useDeferredValue gets stuck with a stale value #35821

@gaearon

Description

@gaearon

Essentially, I see useDeferredValue being stuck and never catching up.

Not sure if it's possible to extract it out of a Next.js app. I think it's probably a React bug because it seems related to core APIs, but I couldn't simplify it past the "render some JSX from server action" repro case.

Demo

Type "hello world".

In dev (npm run dev), the second text area catches up.

In prod (npm run build + npm start), the second text area often gets stuck and never catches up.

bug.mov

Code

Here is a repro case: https://github.com/gaearon/react-udv-bug/

This is the main harness:

export function TestPreviewClient() {
  const [promise, setPromise] = useState<Promise<ReactNode>>(initialPromise);
  const deferred = useDeferredValue(promise);

  function handleChange(value: string) {
    setPromise(renderAction(value));
  }

  return (
    <div style={{ padding: 16, fontFamily: "monospace" }}>
      <textarea
        placeholder="type here"
        onChange={(e) => handleChange(e.target.value)}
        rows={3}
        style={{ width: "100%", fontFamily: "monospace" }}
      />
      <div
        data-testid="deferred"
        style={{ border: "1px solid #ccc", padding: 8, minHeight: 40 }}
      >
        <Suspense fallback={<div>loading...</div>}>
          <Resolved promise={deferred} />
        </Suspense>
      </div>
    </div>
  );
}

Note these helpers:

export function ClientWrapper({ children }: { children: ReactNode }) {
  const t = performance.now();
  while (performance.now() - t < 2) {
    // do nothing
  }
  return <>{children}</>;
}

function Resolved({ promise }: { promise: Promise<ReactNode> }) {
  return <>{use(promise)}</>;
}

And this is the server part:

async function AsyncChild({
  children,
}: {
  children: ReactNode;
}): Promise<ReactNode> {
  await new Promise((r) => setTimeout(r, 1));
  return children;
}

async function Item({ children }: { children: ReactNode }): Promise<ReactNode> {
  return (
    <ClientWrapper>
      <AsyncChild>{children}</AsyncChild>
    </ClientWrapper>
  );
}

export async function renderAction(input: string): Promise<ReactNode> {
  return (
    <Item>
      <div>
        <Item>
          <div>{input}</div>
        </Item>
      </div>
    </Item>
  );
}

You can generally simplify this structure but then it will be harder to reproduce.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions