facebook / react

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

[Umbrella] Releasing Suspense #13206

Open acdlite opened 5 years ago

acdlite commented 5 years ago

Let's use this issue to track the remaining tasks for releasing Suspense to open source.

Last updated: March 24, 2022

Blog post: The Plan for React 18

Completed: React 16

Completed: React 18 Alpha

Completed: React 18

Features that may or may not appear in 18.x

React 18.x (post-18.0): Suspense for Data Fetching

All of the above changes are foundational architectural improvements to <Suspense>. They fill the gaps in the mechanism and make it deeply integrated with all parts of React (client and server). However, they don't prescribe a particular data fetching strategy. That will likely come after the 18.0 release, and we're hoping that to have something during the next 18.x minor releases.

This work will include:

aweary commented 5 years ago

Expose unstable_AsyncMode (maybe?)

Isn't this already exposed?

acdlite commented 5 years ago

I meant remove the unstable_

thoamsy commented 5 years ago

I am looking forward to open source of the unnamed code-splitting library πŸ’―

ryota-murakami commented 5 years ago

What does it mean [Umbrella]?πŸ€”β˜‚οΈ

ghoullier commented 5 years ago

This mean, it's a feature which impact several projects/packages/tools.

ryota-murakami commented 5 years ago

@ghoullier I see, Thank you so much!

JedWatson commented 5 years ago

Hey @acdlite, just a question about how to best prepare for this. Not asking for / expecting any kind of timeline, but wondering:

Are you currently expecting these features to drop into React 16 and be easy to adopt incrementally, like the new Context API that landed with 16.3?

Or are you thinking it'll be something that pushes React to v17 and require more work to adopt?

Asking because I'm working on a roadmap that crosses over significantly with pretty much everything on your list and am trying to work out how to best deal with that.

Also do you have any tips on how to best prepare (in terms of code written today, that wants to be future compatible with these improvements to React) - polyfills / techniques / etc?

(apologies if these questions are answered elsewhere and I've missed them)

donaldpipowitch commented 5 years ago

Adding another question to @JedWatson's questions:

Thank you! ❀️

NE-SmallTown commented 5 years ago

IMO, they will provide a blog post like before before it's been landed.

And I think you don't need to prepare too much because there is no breaking change(it does have many features that maybe would seems different/conflict with current practices, like redux fetch with suspense, but there will be a codemod or easy encapsulation to do this, you know, fb has 3W+ components). And if you watch the talk of @acdlite (about ssr suspense in ZEIT) and @gaearon (about client suspense in iceland), you will know you don't need to worry about too much and it's not invasive.

By the way, you can just search the key 'Umbrella' in the repo and you will find more info like #8830 and #12152

AFAIK the newest release is 16.4.0-alpha.0911da3 from February.

IIRC, this is a misoperation?

sebinsua commented 5 years ago

@JedWatson This comment helped me understand what a developer must do to help ensure that their application's are async safe.

cyan33 commented 5 years ago

I'm working on rolling out the suspense module and new APIs in facebook. In case @acdlite is busy with something else, I'd like to share some of my thoughts of our experience in facebook and answer some questions of @JedWatson.

Are you currently expecting these features to drop into React 16 and be easy to adopt incrementally, like the new Context API that landed with 16.3?

I'm not sure if it will come with React 16 or 17. According to the React team, it's likely to be released before the end of this year, which depends on how well it runs in facebook and how the related API is ready or not. But code-wise, I'm happy to say that it would be easy to adopt, because we've been experimenting for quite a while in facebook. The suspense feature will still work for the existing codebase. But with additional changes (like async rendering), you'll have more bonus that the new feature will bring you.

Do you have any tips on how to best prepare (in terms of code written today, that wants to be future compatible with these improvements to React) - polyfills / techniques / etc?

I'd say the migration is rather incremental and progressive. Like @NE-SmallTown said, we don't want to introduce any breaking changes. That would also be painful to roll out to facebook because we have a so large codebase. But so far, the roll out has been smooth and doesn't require you to do additional changes.

acdlite commented 5 years ago

@JedWatson

Are you currently expecting these features to drop into React 16 and be easy to adopt incrementally, like the new Context API that landed with 16.3?

Incrementally. Always incrementally :) Otherwise there's no way we'd be able to ship this at Facebook.

Here's what I'm expecting:

Client Server-side rendering
Suspense Works everywhere* Same constraints as existing server renderer
Async rendering Opt-in using <AsyncMode> Same constraints as existing server renderer

*In sync mode, delayMs is always 0. Placeholders show up immediately.

Suspense will work without any changes to your existing components. At one point we thought we might require <StrictMode> compatibility, but during our internal testing we discovered one of the best ways to upgrade to strict mode was to use Suspense. Chicken-egg dilemma. So we found a way to make it work even outside of strict mode.

So the idea is that users will start migrating to Suspense even before they're ready to migrate to asynchronous rendering. Then once a subtree is ready, they can opt-in by wrapping in <AsyncMode>.

For new apps, though, the story is different: go async by default. We'll introduce a new root API (a replacement for ReactDOM.render) that is async only.

There will be an awkward period after the initial release where many third-party frameworks (Redux, Apollo, React Router...) may not work properly in async mode. That might hurt adoption for a while. But the idea is that the new features will be so compelling that it won't take long for libraries to either adapt or be superseded by an async-compatible alternative.

Also do you have any tips on how to best prepare (in terms of code written today, that wants to be future compatible with these improvements to React) - polyfills / techniques / etc?

Wrap everything in <StrictMode> and make sure there are no warnings. We'll have more detailed migration guides as we get closer to release.

hwillson commented 5 years ago

There will be an awkward period after the initial release where many third-party frameworks (Redux, Apollo, React Router...) may not work properly in async mode.

Apollo doesn't do awkward - we'll be ready! πŸ•ΊπŸ˜³

Seriously though, we :heart: all things React, so making sure we're in-line with these changes for the initial release is not only a high priority, but it's also something we're super excited about! Thanks for all of your amazing work on this @acdlite!

markerikson commented 5 years ago

I'll chime in and say that the Redux team is working on async compat for React-Redux.

I laid out a potential roadmap at https://github.com/reduxjs/react-redux/issues/950 . TL;DR:

We'd appreciate more eyes on our WIP, and hopefully people can give us some more feedback and discussion on how they're looking at using Redux with React Suspense and async rendering so we can make sure use cases get covered properly. We're also hoping to have some more discussions with the React team about exactly what constraints we need to work with, and it'd be helpful if we could get some sample apps that would let us see what problems we need to solve for all this to work correctly.

anymost commented 5 years ago

looking forward to the release of Async rendering and Suspense

ghost commented 5 years ago

@acdlite Also question about suspense and async rendering. My question is once they are introduced and one starts writing apps with that new version of react: does it mean that react API and the way people code in react will change too? (even if they don't plan to use features of suspense and async rendering?)

I assume it can be trickier to write react code with suspense and async rendering (maybe due to some new API or other constraints), and for those who don't need it, why force them to use react in a new way? And not allow them to code in react the way they do now?

gaearon commented 5 years ago

I assume it can be trickier to write react code with suspense

Have you had a chance to watch the second half of my talk? I'd say quite the opposite β€” it's way less trickier to use suspense for data fetching than anything else (including Redux, local state, or some other library).

ghost commented 5 years ago

@gaearon I haven't. I was speaking more in theory. Imagine there is already set of people who know react. If people don't need the feature of async rendering and suspense, why force them learn "new" react? Especially if the "new" react is tricker to use? But: I am not well informed so I might be wrong say about the "trickier" part - I am just sharing some of my thoughts :).

In a way I am saying if 10% of apps need the feature of Suspense and async rendering, why in those other 90% cases force people to learn "new" react? But again I might be wrong, since I didn't gather much info about suspence and async rendering yet.

gaearon commented 5 years ago

I think it's hard to have a conversation if you haven't looked at my demos yet.

To be clear: there's no "new React", these features don't break any existing patterns πŸ™‚. They are additive. You don't need to write code in a completely different way to use those features either β€” although some of them only work if you use modern lifecycle methods.

While this is not directly related to your concern, I disagree they're "trickier to use". I think suspense is much simpler to use than any other loading mechanism that currently exists. That's the reason I'm so excited about it. But again, you don't have to use any of the new features if you don't want to. Old patterns will keep working.

I really do recommend watching my talk. I'm sure this will make a lot more sense once you see these features in action.

ghost commented 5 years ago

@gaearon

All old patterns keep working.

Thanks for feedback Dan. Yeah that is how I thought, I suppose if people don't need those features they should be able to write the way they used to before those features were added.

good luck.

ghost commented 5 years ago

Hey Dan(@gaearon), I am not nitpicking but want to figure it out. Above you said:

But again, you don't have to use any of the new features if you don't want to. Old patterns will keep working.

Which would suggest that I can code in new React the same way I did in "old" React, e.g. I could use the life cycle methods in the same way, etc. right?

However, here, bvaughn says that getDerivedStateFromProps (or componentWillReceiveProps) could be called many times for one update, hence his solution not to fetch data inside it.

So my question is, after all, it does seem we can't use the new React in exactly the same way as before right? Because AFAIK in current react componentWillReceiveProps doesn't get called many times for one update, isn't it?

markerikson commented 5 years ago

@giorgi-m : yes, the lifecycle methods are changing, but the point is that Suspense itself is an opt-in feature. All your existing React render methods and React's rendering behavior will work as-is. However, if you opt in by adding an <AsyncMode> tag to a part of your app, and you begin using Suspense's approach for indicating async data needs, then you can take advantage of it. None of that happens if you don't add that to your codebase.

TrySound commented 5 years ago

@giorgi-m componentDidUpdate should be used instead of componentWillReceiveProps or getDerivedStateFromProps.

ghost commented 5 years ago

@markerikson So you say that what bvaughn said here, that getDerivedStateFromProps can be called many times for one update, is not necessarily the case, if I haven't enabled the<AsyncMode/>? (sorry for asking such questions just they popup to me from time to time, and didn't find resource which would cover all).

ps. bvaughn also didn't mention the optionality of that in the linked thread, hence it raised my suspicion.

pshrmn commented 5 years ago

Should a method for enqueueing asynchronous updates (e.g. deferSetState() for class components as opposed to renderer-specific unstable_deferredUpdates()) be added to the core checklist?

From my understanding, any updates for fibers in async mode will be asynchronous, which in theory means that deferSetState() would be unnecessary. However, the unstable-async/suspense demo mixes a synchronous update and an async update and I'm not sure how that can be accomplished in async mode (for "universal" components).

gaearon commented 5 years ago

It’s in the check list for the time slicing umbrella.

thysultan commented 5 years ago

Support promise as a component type

Related to this, when you have:

const PromiseType = new Promise(() => {})
class A extends Component {
    componentDidMount() {}
    componentDidUpdate() {}
    render() {
        return <div><PromiseType></PromiseType></div>
    }
}

Are there any heuristics as to when componentDidMount and componentDidUpdate lifecycles would get called.

  1. When all children have been resolved(including the promise), which in this case means they won't get called given the promise is never resolved?
  2. When all Immediate host children have been rendered?
markerikson commented 5 years ago

@thysultan : componentDidMount and componentDidUpdate are called in the commit phase, when a UI tree has been fully rendered and applied to the DOM.

So, based on my understanding of Suspense, I think the answer is that the A instance would never actually mount. If PromiseType did get resolved, but one of its further descendants also attempted to wait for a promise that never resolves, it would again never mount. Thus, cDM and cDU would never be executed in those examples.

(Someone feel free to correct me if my assumptions are wrong here :) )

gaearon commented 5 years ago

Yea, componentDidMount or componentDidUpdate only execute in the commit phase which only executes after the whole tree has been resolved. This tree might include some placeholders that you've explicitly put there (depending on whether something inside them still suspends after we've waited long enough) β€” but if you explicitly render a child without a placeholder around it, you can never end up in a situation where it's "not ready".

Kingdutch commented 5 years ago

I am really looking forward to being able to play with this (even browsed through a lot of source code only to figure out you hadn't put a working version of this on the world wide web yet).

Is there anything we can do to help get this released? :D

gaearon commented 5 years ago

You can compile from master if you wanna play. See instructions in fixtures/unstable-async/suspense

Kingdutch commented 5 years ago

@gaearon Am I correct that this is in its current state client side only (so more work to be done to support server side rendering)?

EDIT, found the answer: For anyone else eagerly looking for using Suspense in SSR for universal apps. I found this comment by Dan that lets us know this is client side only for now. However, that comment also points to https://www.youtube.com/watch?v=z-6JC0_cOns which talks about the possible implications for SSR.

gaearon commented 5 years ago

We're actually starting some work related to the SSR case soon.

luisherranz commented 5 years ago

Imagine this scenario in an async React app running in a low-end mobile device:

  1. There's an external ad loading, using all the CPU (πŸ˜“)
  2. The user clicks on a link and the router triggers a new render
  3. React is in async mode, so it waits until Chrome/Safari gives it permission to use the CPU, but the ad keeps loading for 3 seconds more
  4. The user thinks the app is not working

This problem could be avoided using the same <Placeholder> technique discussed for Suspense, showing a spinner after 1 seconds for example.

Has this scenario been considered? Will <Placeholder> work for slow async renders?

gaearon commented 5 years ago

@luisherranz There are two mechanisms to prevent this:

  1. React has a deadline associated with every update. Updates from clicks and other interactions like this should flush within ~150ms unless you explicitly opt into a longer deadline (e.g. for non-essential updates). So React will synchronously force the flush if something is hogging the thread.

  2. React doesn't actually use requestIdleCallback anymore because indeed the browsers aren't aggressive enough about scheduling it. The exact scheduling approach might also change over time, but this is definitely something we care about.

luisherranz commented 5 years ago

Many thanks for the quick answer Dan.

  1. React has a deadline associated with every update

Awesome. Is there already an API in place we could test?

  1. React doesn't actually use requestIdleCallback anymore because indeed the browsers aren't aggressive enough about scheduling it.

That's what we've experimented as well. Sometimes in a crowded app with external ads and embeds from twitter or youtube it may take several seconds until the requestIdleCallback is called and the render is finished. So πŸ‘ to that.

BTW, for us there's another use case related to your first answer: we are trying to use lazyloads with two offsets. The first one triggers an async render, the second one triggers a sync render if the async hasn't finished. Something like this:

  1. The user scrolls down and it's at 1200px from a heavy element: for example the next post of a blog.
  2. The first offet triggers an async render of the next post.
  3. The user keeps scrolling down and it's at 600px of the next post.
  4. The second offset triggers: If the async post has finished (componentDidMount called) nothing happens but if it has not, it triggers a sync render of the whole post.

So instead of time, we'd like to control the flush with a second trigger. Does it make sense? Could it be possible?

gaearon commented 5 years ago

Is there already an API in place we could test?

I'm not sure if you mean on master or not (async mode is not officially available in npm releases), but expiration time is assigned automatically to every update. For events like clicks it's ~150ms in prod mode. You can't set it, although you could opt into a deferred (longer) update if you want with a special unstable API.

So instead of time, we'd like to control the flush with a second trigger. Does it make sense? Could it be possible?

Yeah it's possible. I described a mechanism like this here: https://github.com/oliviertassinari/react-swipeable-views/issues/453#issuecomment-417939459.

luisherranz commented 5 years ago

I'm not sure if you mean on master or not (async mode is not officially available in npm releases)

Yes, we're using the npm package with <unstable_AsyncMode>, flushSync and unstable_deferredUpdates.

Please, start releasing alpha/beta versions with these changes to npm! πŸ™

Yeah it's possible. I described a mechanism like this here: oliviertassinari/react-swipeable-views#453 (comment).

Brilliant. Much better than our current implementation using unstable_deferredUpdates :)

Can't wait to start using <div hidden>. You guys are doing an amazing job.

gaearon commented 5 years ago

@luisherranz Be careful, those things are unstable. May have bugs or be pretty inefficient (e.g. a crucial optimization β€” "resuming" β€” is not implemented yet). We're removing unstable_deferredUpdates too in favor of the new schedule module (schedule.unstable_scheduleWork).

luisherranz commented 5 years ago

Yep, we are using it only for a small portion of the app and testing intensively but so far so good :)

langpavel commented 5 years ago

MAY BE OFFTOPIC: requestIdleCallback is called only 20 times per second - Chrome on my 6x2 core Linux machine, it's not really useful for UI work. requestAnimationFrame is called more often, but specific for the task which name suggests.

gaearon commented 5 years ago

We’ve stopped using requestIdleCallback for this reason.

TejasQ commented 5 years ago

What are y'all using instead of requestIdleCallback?

NE-SmallTown commented 5 years ago

@TejasQ https://github.com/facebook/react/tree/master/packages/scheduler

TejasQ commented 5 years ago

Ah, of course. Silly me. I was wondering what browser API the scheduler module uses under the hood instead of requestIdleCallback. I should've presented the question more clearly. πŸ˜…

TejasQ commented 5 years ago

Oh wow, found it. https://github.com/facebook/react/blob/eeb817785c771362416fd87ea7d2a1a32dde9842/packages/scheduler/src/Scheduler.js#L212-L222

That is next-level cool.

nilshartmann commented 5 years ago

Hi,

while trying to understand Suspense I recognized that I have no idea what part of an app is actually "suspended" when using the Suspend component 😳😱.

In the following example I would expect Title to be visible immediately, Spinner after 1000ms and UserData after ~2000ms (as "loading" the data for that component takes 2000ms). But what I see is that Title first appears together with Spinner after 1000ms.

// longRunningOperation returns a promise that resolves after 2000ms
const UserResource = createResource(longRunningOperation);

function UserData() {
  const userData = UserResource.read(cache, "Lorem Ipsum");
  return <p>User Data: {userData}</p>;
}

function Spinner() {
  return <h1>Fallback Loading Spinner</h1>;
}

function Title() {
  return <h1>Hello World</h1>;
}

function App() {
  return (
    <React.Fragment>
      <Title />
      <Suspense maxDuration={1000} fallback={<Spinner />}>
        <UserData />
      </Suspense>
    </React.Fragment>
  );
}

unstable_createRoot(document.getElementById("mount")).render(<App />);

(You can find the complete example that uses React 16.6.0-alpha.8af6728 here on codesandbox)

Is there a way to make Title immediately visible and "suspend" only the other part of the application? Or did I misunderstand maybe Suspense completely? (If there is a better way to ask this kind of questions, please let me know)

Thanks!

TejasQ commented 5 years ago

Hi @nilshartmann! Great question!

Is there a way to make Title immediately visible and "suspend" only the other part of the application?

If I understand correctly, you'll need to explicitly tell React to not wait before flushing Title to the DOM as in this example in order to make Title immediately visible and "suspend" only the other part of the application by wrapping the parts that you'd expect to be immediately rendered in a <Suspense maxDuration={0}>.

I imagine this is the case because of some underlying lower-level scheduler mechanics? I'd also love to understand this better, but that should solve your problem for now.

I'm excited to hear what's actually going on there.

(If there is a better way to ask this kind of questions, please let me know)

I'm not sure there is. πŸ˜„ It seems quite clear to me. Thanks for asking!

karlhorky commented 5 years ago

@TejasQ In my browser, loading your example renders the fallback spinner right away. Shouldn't it load after 1000ms?

nilshartmann commented 5 years ago

@TejasQ thanks for your answer, but @karlhorky is right: now spinner shows up immediately.