Repository: react
Issue: Possibility to set min duration of Suspense fallback
Labels: Type: Feature Request,Component: Concurrent Features,Resolution: Backlog

**Do you want to request a *feature* or report a *bug*?**
Feature

**What is the current behavior?**
I have played a bit with Concurrent Mode and the Suspense API.
Really exiting features and I look forward to use them in a stable release. Thank you for everything you are doing!

Regarding the `Suspense` component, could it be nice to have a property (both in Concurrent Mode and in "normal/synchronous" mode) which would allow us to set the minimum duration of the `Suspense` fallback UI in case the fallback UI ever gets rendered?

**What is the expected behavior?**
Let me do an example. Try clicking on the `Next` button in this codesandbox: 
https://codesandbox.io/s/cold-monad-ifr29.

You will see that the `Suspense` fallback UI is rendered and stays in the tree just for a little moment (`~200ms`) because both promises resolve in `1200ms`, while `useTransition` has a `timeoutMs` of 1 second.
In my opinion, this is a bit unpleasant to the eye.

Wouldn't it be nicer if we could tell the `Suspense` component something like "If you ever render the fallback, show it for at least N millisec."? E.g.:

```jsx
...
function ProfilePage({ resource }) {
  return (
    <Suspense fallback={<h1>Loading profile...</h1>}
      // If the fallback ever gets rendered,
      // it will be shown for at least 1500 millisec.,
      // even if the promise resolves right after rendering the fallback.
      fallbackMinDurationMs={1500}>
      <ProfileDetails resource={resource} />
      <Suspense fallback={<h1>Loading posts...</h1>}>
        <ProfileTimeline resource={resource} />
      </Suspense>
    </Suspense>
  );
}
...
```

Consider an animated spinner used as a fallback of `Suspense`, if it happens that the promise resolves just a few milliseconds after rendering the fallback like above, the spinner will be rendered and suddenly disappear, without completing its animation cycle and showing an incomplete animation.

Whereas, if we could keep the spinner in the tree for at least `fallbackMinDurationMs` millisec. once rendered, we could improve its appearance in such cases.

The `Suspense` component responsible for rendering the fallback would have to wrap the caught Promise in a promise which would look something like this:

```js
function maxDelayFallbackPromise({
  promise,
  timeoutMs, // ---> This would be the value of `useTransition`'s `timeoutMs`
  onFallback = () => {}, // ---> This code would run in case `timeoutMs` exceeds (i.e. when `Suspense`'s fallback UI is rendered)
  fallbackMinDurationMs
} = {}) {
  // Generate a unique identifier, like a string, a number, in order to identify which promise resolves first...
  const uniqueIdentifier = `promise_value_${Math.random()}`
  return Promise.race([
    promise,
    timeout(timeoutMs).then(() => uniqueIdentifier)
  ]).then(value => {
    if (value === uniqueIdentifier) {
      onFallback()
      return minDelayPromise(promise, fallbackMinDurationMs)
    }
    else {
      return value
    }
  })
}
```

Where `timeout` and `minDelayPromise` are:

```js
function timeout(delayMs) {
  return new Promise(resolve => setTimeout(resolve, delayMs))
}

function minDelayPromise(promise, minDelay) {
  return Promise.all([
    promise,
    timeout(minDelay)
  ]).then(([value]) => {
    return value
  })
}
```

This could also apply to the `isPending` flag of `useTransition`...

Do you think such a feature could improve the UX in such cases?

**UPDATE - 04/09/2022** - For anyone looking at this issue, there is a workaround to achieve this fallback min duration behaviour in React 17 🎉 , described here: https://github.com/facebook/react/issues/17351#issuecomment-1236303278