facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
229.88k stars 47.06k forks source link

Bug: Inconsistent behavior with Promises near the root #26886

Open gaearon opened 1 year ago

gaearon commented 1 year ago

Just jotting down some cases I found confusing. Ideally for each case, it should either work, or should fail in some obvious way.

Working: startTransition + 1000ms Promise root

import { startTransition } from "react";
import { createRoot } from "react-dom/client";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

let promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve(<h1>hi</h1>);
  }, 1000);
});

startTransition(() => {
  root.render(promise);
});

https://codesandbox.io/s/goofy-rui-8g6sxh?file=/src/index.js

Working: startTransition + root component + 5000ms Promise child

import { startTransition } from "react";
import { createRoot } from "react-dom/client";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

let promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve(<h1>hi</h1>);
  }, 5000);
});

function Foo() {
  return promise;
}

startTransition(() => {
  root.render(<Foo />);
});

https://codesandbox.io/s/epic-cookies-v94rk2?file=/src/index.js

Working: No startTransition + root component + 5000ms Promise child

import { createRoot } from "react-dom/client";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

let promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve(<h1>hi</h1>);
  }, 5000);
});

function Foo() {
  return promise;
}

root.render(<Foo />);

https://codesandbox.io/s/zen-allen-2u1nr7?file=/src/index.js

Working: startTransition + root component + 1000ms Promise in state

import { useState, startTransition } from "react";
import { createRoot } from "react-dom/client";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

function Foo() {
  const [promise, setPromise] = useState(
    new Promise((resolve) => {
      setTimeout(() => {
        resolve(<h1>hi</h1>);
      }, 1000);
    })
  );
  return promise;
}

startTransition(() => {
  root.render(<Foo />);
});

https://codesandbox.io/s/immutable-moon-2h3dqz?file=/src/index.js

Working: startTransition + root component + 1000ms Promise in state + use

import { use, useState, startTransition } from "react";
import { createRoot } from "react-dom/client";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

function Foo() {
  const [promise, setPromise] = useState(
    new Promise((resolve) => {
      setTimeout(() => {
        resolve(<h1>hi</h1>);
      }, 1000);
    })
  );
  return use(promise);
}

startTransition(() => {
  root.render(<Foo />);
});

https://codesandbox.io/s/hungry-carlos-1n8hqu?file=/src/index.js

Crashes: startTransition + 5000ms Promise root

This doesn't work (with a confusing crash):

import { startTransition } from "react";
import { createRoot } from "react-dom/client";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

let promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve(<h1>hi</h1>);
  }, 5000); // <--- I increased the delay
});

startTransition(() => {
  root.render(promise);
});

https://codesandbox.io/s/busy-torvalds-xgbcgh?file=/src/index.js

Crashes: No startTransition + 1000ms Promise root

This doesn't work (with a confusing crash):

import { createRoot } from "react-dom/client";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

let promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve(<h1>hi</h1>);
  }, 1000);
});

root.render(promise); // No startTransition

https://codesandbox.io/s/serene-payne-677ghp?file=/src/index.js

Never resolves: No startTransition + root component + 1000ms Promise in state

import { useState } from "react";
import { createRoot } from "react-dom/client";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

function Foo() {
  const [promise, setPromise] = useState(
    new Promise((resolve) => {
      setTimeout(() => {
        resolve(<h1>hi</h1>);
      }, 1000);
    })
  );
  return promise;
}

root.render(<Foo />);

https://codesandbox.io/s/musing-chaplygin-2udvbx?file=/src/index.js

Never resolves: No startTransition + root component + 1000ms Promise in state + use

import { use, useState } from "react";
import { createRoot } from "react-dom/client";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

function Foo() {
  const [promise, setPromise] = useState(
    new Promise((resolve) => {
      setTimeout(() => {
        resolve(<h1>hi</h1>);
      }, 1000);
    })
  );
  return use(promise);
}

root.render(<Foo />);

https://codesandbox.io/s/blue-butterfly-3xywp2?file=/src/index.js:0-400

shriphad-rao commented 9 months ago

Hey @gaearon

I was wondering if there has been any progress in identifying a solution. I'm keen to contribute and assist in implementing a fix once we have a clear direction. Could anyone provide an update on potential solutions or further steps that need to be taken? I'm ready to help in any way I can, following the project's contribution guidelines.

Thank you for your efforts in addressing this issue. Looking forward to any updates or guidance on how I can contribute effectively.