React: [Umbrella] Releasing Suspense

Created on 13 Jul 2018  Ā·  83Comments  Ā·  Source: facebook/react

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

Initial release (MVP)

Core

  • [x] API to read context from within any render phase function (@acdlite) [#13139]
  • [x] Hide timed-out content instead of deleting it (@acdlite) [#13120]
  • [ ] Automatic injection of context providers per React root (@acdlite) [#13293]
  • [ ] Remove unstable_ prefix from AsyncMode (maybe?)
  • [ ] Support for synchronous thenables, and for promises that resolve before the render phase is complete.

    • [ ] Confirm that a synchronous thenable that throws an error is handled correctly

  • [ ] Confirm it works with <div hidden> [#13089]
  • [ ] Why does clicking on several detail links in the fixture one by one eventually causes a big placeholder even if I wait for each of them for less than the placeholder delay before clicking the next one (see tweet)?

Simple Cache Provider

  • [ ] Cache invalidation (@acdlite) [#13337]
  • [ ] Subscriptions (@acdlite) [#13337]
  • [ ] Decide on actual name

Code splitting

  • [x] Support promise as a component type
  • [x] (maybe) Open source lazyLoadComponent?

Test renderer

  • [ ] Finalize public APIs for flushAll, yield, etc

    • Tentative plan is to publish custom matchers for each of the major testing frameworks, a la #13236.

Docs

  • [ ] Blog post
  • [ ] React.Placeholder
  • [ ] simple-cache-provider
  • [ ] Unnamed code-splitting library

Follow ups

Soft expiration (https://github.com/facebook/react/issues/14248)

  • [ ] Implement an API for in-place loading indicators that aren't ancestors
  • [ ] Make sure there's a way to avoid flashing the inline spinner if it's fast enough

Streaming server renderer

  • [ ] Implement a streaming server renderer like the one in @acdlite's ZEIT talk
  • [ ] Partial hydration

Related: Time Slicing Umbrella (https://github.com/facebook/react/issues/13306)

React Core Team Umbrella

Most helpful comment

@mschipperheyn

This is a multi-year project. The honest answer is that it spawned way more work than we thought when we started on it two years ago.

But the good news is that because we now heavily use it in production, the missing pieces are clear and we see the end of the tunnel. It's not theoretical ā€” there is a finite set of things that we need to finish before we can comfortably say it's ready for broad adoption.

Here's a rough state of different workstreams today:

  • <Suspense> API for code splitting with lazy. (shipped in React 16.6)

    • As you might know, you can use this one already.

  • Concurrent Mode APIs, e.g. createRoot and useTransition. (available in experimental releases)

    • Compatibility solution for Flux-like libraries. (in progress @bvaughn, https://github.com/reactjs/rfcs/pull/147)

    • Changing the priority model to a more sensible one. (in progress @acdlite, https://github.com/facebook/react/pull/18612)

    • Only allow the last pending transition to finish. (in progress @acdlite)

    • Offscreen API (in progress @lunaruan)

    • Fire effects when hiding/showing content for Suspense

    • Fire effects when hiding/showing content for Offscreen

    • Show and hide Portal children when needed

    • Align with the ongoing standardization work for Scheduling (not started)

    • Fix major known bugs (in progress @gaearon and @acdlite)

    • Change event semantics. (in progress @sebmarkbage @trueadm)

    • Delegate to roots instead of document to enable more gradual adoption (in progress, @trueadm)

    • Flush discrete events in capture phase.

    • Consider getting default priority from event to play nicer with imperative code.

    • Finalize other API semantics and defaults. (not started)

    • Update typings and documentation.

  • Suspense for Data Fetching

    • Low-level support for signaling a component isn't ready to render (technically available in stable React too, but this API isn't considered stable).

    • Server renderer immediately flushes Suspense fallbacks (available in experimental releases)

    • A solution for GraphQL use cases (Relay Hooks, shipped).

    • A solution for non-GraphQL use cases (in progress @sebmarkbage in collaboration with Next.js).

    • A bundler integration for data-driven dependencies. (in progress)

    • Finalize Blocks API, including context.

    • A generic caching solution. (not started)

    • Some kind of a router integration.

It almost feels like "It's in production at Facebook, so we're done".

I can see how it could look this way, although tbh this reading is a bit demoralizing. :-) We've been working non-stop on this for all the past months, and many of the technical aspects are either finished or close to being finished. Most of the remaining works falls into two categories:

  • Fixing the flaws we discovered in the initial designs before we cement them in a stable release. If we were to release what we have now, we'd have to follow through with significant breaking changes in a few months. That would only be confusing.

  • Ecosystem compatibility and good defaults. It doesn't help if we release something that nobody can use today because it doesn't work with their libraries or existing approaches. So the bulk of the work (e.g. compatibility with Flux-like libraries via useMutableSource, picking better event semantics, shipping a recommended caching strategy) is in ensuring you would actually be able to use what we released. This is a long tail.

In terms of "can you use today"... Technically, you can use all of this today. We do. Specifically, we use Relay Hooks and Concurrent Mode. We still have significant planned changes and known issues, so the current state isn't meeting the bar where we would consider it ready for broad adoption. Of course, if you don't mind having bugs or APIs changing right under your hands, you're welcome to use the @experimental releases just like we do.

I wouldn't say Facebook is in a special position here in terms of us "being done". Quite the opposite, we're not done ā€” but internally, we're willing to tolerate churn and build on top of a moving train because that's how we know what we're building is solid. Without this heavy dogfooding, the flaws we discovered in six months would take several years to discover and redesign around.

To sum it up: there's more work to do.

All 83 comments

Expose unstable_AsyncMode (maybe?)

Isn't this already exposed?

I meant remove the unstable_

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

What does it mean [Umbrella]?šŸ¤”ā˜‚ļø

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

@ghoullier I see, Thank you so much!

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)

Adding another question to @JedWatson's questions:

  • We also don't need/expect to get a timeline for a stable release, but would it be possible/useful to get a new prerelease? (AFAIK the newest release is 16.4.0-alpha.0911da3 from February.)

Thank you! ā¤ļø

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?

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.

@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.

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!

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:

  • React-Redux 5.1 will hopefully work with <StrictMode> with no warnings (current PR: https://github.com/reduxjs/react-redux/pull/980 )
  • 6.0 will be an internal rewrite to use the new context API, add ref forwarding, and possibly other changes, but try to keep as much of the current public API as possible (ie, <Provider> and connect() ). We'll see how well that works with async rendering, and figure out the best path forward. (My prior proof-of-concept PR is at https://github.com/reactjs/react-redux/pull/898 , but we'll probably redo it based on other lessons learned from the 5.1 work.) It's likely that this release would require React 16.5 as a minimum, due to the need for new context and probably also the as-yet unreleased "read context from lifecycle methods" PR that was just merged.
  • After that, we're open to ideas for a different React-Redux API (yes, yes, that possibly includes render props, people).

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.

looking forward to the release of Async rendering and Suspense

@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?

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).

@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.

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.

@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.

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?

@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.

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

@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.

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).

Itā€™s in the check list for the time slicing umbrella.

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?

@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 :) )

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".

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

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

@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.

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

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?

@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.

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?

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.

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.

@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).

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

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.

Weā€™ve stopped using requestIdleCallback for this reason.

What are y'all using instead of requestIdleCallback?

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. šŸ˜…

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!

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!

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

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

Yikes! I missed that. I tried. šŸ˜…Let me take another look at it and get back to you. I must have missed something. šŸ¤·ā€ā™‚ļø

~Update: I'm trying to figure it out here and we can collaborate if anyone's interested to do it together in realtime.~

Second update: @philipp-spiess and I have looked at it and I am genuinely stumped. I still don't understand it. At this point, I'm not sure if it's a bug since this is, in fact, an unstable_ and alpha feature, or if it's something that I am simply not seeing.

In either case, I feel like the core team will either have helpful answers, or be able to use your question to make React even better/more approachable.

Let's see what they have to say. šŸ˜„ Thanks for pointing this out, @nilshartmann!

Was this released as part of React v16.6? The blog post shows example code using Suspense:

import React, {lazy, Suspense} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));

function MyComponent() (
  <Suspense fallback={<div>Loading...</div>}>
    <OtherComponent />
  </Suspense>
);

Was this released as part of React v16.6?

Only the lazy loading use case, and only in sync mode. Concurrent mode is still WIP.

@nilshartmann

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).

I do think you're a bit confused about what maxDuration does. It's a new mental model but we haven't had time to document this yet. So it'll keep being confusing for a while until concurrent mode is in a stable release.

Congrats on announcing the hooks proposal. I'd like to share something with the team. A while ago I released a component called React Async, which has features similar to Suspense. Essentially it handles Promise resolution, and provides metadata such as isLoading, startedAt and methods like reload and cancel, all with a declarative API (and a useAsync hook is on the way).

Now my main concern is how this will integrate with Suspense. For the most part I can probably use the Suspense APIs from React Async and provide users with the familiar and simple React Async API, while offering Suspense's scheduling features for free. For what I've seen, I genuinely think the React Async API is more sensible and approachable compared to the more abstract Suspense APIs. Essentially React Async tries to offer a more concrete API that works for a slightly smaller subset of use cases.

I was surprised to learn of the React Cache library. For React Async I deliberately did not include a cache mechanism but chose to deal with vanilla Promises. Adding caching on top of that is fairly easy.

Finally I'm concerned about accessing Suspense features from custom hooks. Suspense seems to rely heavily on several built-in components, which makes it impossible (I think?) to use these from a hook. Will there be Suspense hooks? Or is there some other way to integrate the two?

Hello. How can I test code with Suspense/Lazy?
now renderer.create(...)toTree() throws
"toTree() does not yet know how to handle nodes with tag=13"

Why the props maxDuration in Suspense is only used in the Concurrent Mode rather than both of sync and concurrent mode. Can anyone help explain?

(Right now) it means how long Concurrent Mode is allowed to leave this tree pending before forcing it to commit -- it effectively controls the time slicing deadline, and time slicing doesn't exist in Sync mode. Waiting before committing the tree would necessarily make the commit... not Sync.

I've been using Suspense in an internal application for data fetching and very quickly come across the reason why it is not meant to be used for data fetching yet.

Eventually, though, it is meant to be used for fetching data. Given that it seems unlikely the API is going to change significantly except for perhaps the cache provider, how is Suspense meant to work if you need to modify the data after you've fetched it?

As an example, here is a really awful hook from my application.

function useComponentList(id) {
  const incomingComponents = useSuspenseFetch(
    React.useCallback(() => getComponentAPI().listComponents(id), [id])
  )

  const map = React.useMemo(
    () =>
      Map(
        (incomingComponents || []).map(component => [component.id, component])
      ),
    [incomingComponents]
  )

  return useCacheValue(map)
}

This hook:

  1. Fetches data using the given callback from the given endpoint
  2. Transforms that data into an ImmutableJS Map - Since this is potentially expensive, I memoize the operation.
  3. Returns the map wrapped in useCacheValue, which is the particularly awkward bit.

useCacheValue looks like this:

export default function useCacheValue(value) {
  const [state, setState] = React.useState(value)
  React.useEffect(() => {
    setState(value)
  }, [value])

  return [state, setState]
}

with the idea being that it is a hook that will respond to value changing (which indicates that the data was refetched) but allows the user to modify the react apps representation of that state. In a way, it acts like a very bad cache (hence the name).

I'm struggling to see how this pattern works well with Redux in its current state. Has there been any discovery into how this might look when written by a programmer that is not me and when suspense is 'ready' for data fetching? As it stands, this is much more laborious than using Redux on its own with imperative fetching flags.

This probably gets a lot simpler once Redux has its own hooks since the main difficulty in making the two play together is that Redux uses a HOC with a context that is not meant to be exposed, but I'd still like to see what the official answer is :)

Suspense is meant to work with an external cache (not a Hook driven by state). Weā€™ll provide a reference implementation of such a cache that works for simple use cases. Relay will implement its own caching mechanism. Any other tools (such as Apollo) will be able to also implement their own cache thatā€™s compatible, possibly getting inspired by these two implementations.

Mutation/invalidation isnā€™t the only question that needs answers. We also need to think about: when to show spinners, common patterns like ā€œinline indicatorā€ which may be outside the suspended tree, coordinating loading states (for things that need to unlock in a top-down order or come in as theyā€™re ready), streaming rendering of lists, how this affects partial hydration, and so on. Weā€™re working on these things but thereā€™s no ā€œofficial recommendationā€ on any of them yet. When there is, youā€™ll know it from the blog where we announce updates.

As a side note, Suspense for data fetching is a sufficiently different mental model than what people might be used to. I donā€™t think itā€™s fair to expect itā€™ll necessarily be as powerful when integrated with very unconstrained mechanisms like Redux. But weā€™ll see. Itā€™s hard to say anything right now.

@gaearon When you say "we're working on these things", is there an issue I can subscribe to or are these discussions happening in private?

Thanks, @gaearon :)

@ntucker As always, you can watch ongoing activity as PRs. For example: https://github.com/facebook/react/pull/14717, https://github.com/facebook/react/pull/14884, https://github.com/facebook/react/pull/15061, https://github.com/facebook/react/pull/15151, https://github.com/facebook/react/pull/15272, https://github.com/facebook/react/pull/15358, https://github.com/facebook/react/pull/15367, and so on. We try to put some descriptive information into each PR, and you can see the behavior changes from tests. The documentation bar for experiments is low for several reasons.

We'll post more complete explanations about the chosen model after we're more confident that it actually works. I don't think it'll be productive either for us or for the community if we painstakingly describe every experiment in detail as it happens. Most of our first experiments fail, and documenting and explaining each and every one of them would slow our work to a crawl.

It is even worse that this often results in people building a mental model around something that we later realize doesn't work in the originally designed way. (Like it's happening with maxDuration which we just removed.) So we'd prefer to hold off sharing half-baked ideas until it's a good use of both your time and our time. This is consistent with how we developed React in the past too. When something is truly ready (even for a theoretical writeup of the mental model), we'll focus all our attention on documenting and explaining it.

As a side note, Suspense for data fetching is a sufficiently different mental model than what people might be used to. I donā€™t think itā€™s fair to expect itā€™ll necessarily be as powerful when integrated with very unconstrained mechanisms like Redux. But weā€™ll see. Itā€™s hard to say anything right now.

@gaearon, fortunately, Suspense's mental model matches with my own perfectly. Very excited for that piece of the puzzle to fall into place. Thank you for all your hard work!

The roadmap announced last November (https://reactjs.org/blog/2018/11/27/react-16-roadmap.html) indicated that the "concurrent" version of Suspense was slated for Q2 2019. We are now well in Q3 2019. Is there an update we can get in terms of definitely not Q3, or maybe Q3, etc.?

This was the latest roadmap update I could find: https://reactjs.org/blog/2019/08/08/react-v16.9.0.html#an-update-to-the-roadmap

We provided an experimental release back in October: https://reactjs.org/docs/concurrent-mode-intro.html. This is the same build we're running in production. There's still more work to do (both in terms of tweaking the API and building higher-level APIs) but you can start playing with it if you want.

Suspense is killing me

@gaearon I understand that you use it in production. But I'm very reluctant to use "experimental" code in production. You guys are not being clear about the roadmap, status, progress, timings etc. Is this alpha, beta, RC quality? This term "experimental" says "don't touch this" to me.

We are all banking our businesses on this code and I'm sure we're all as swamped as you guys. A little clarity, a blog, SOMETHING would really help. It almost feels like "It's in production at Facebook, so we're done".

@mschipperheyn

This is a multi-year project. The honest answer is that it spawned way more work than we thought when we started on it two years ago.

But the good news is that because we now heavily use it in production, the missing pieces are clear and we see the end of the tunnel. It's not theoretical ā€” there is a finite set of things that we need to finish before we can comfortably say it's ready for broad adoption.

Here's a rough state of different workstreams today:

  • <Suspense> API for code splitting with lazy. (shipped in React 16.6)

    • As you might know, you can use this one already.

  • Concurrent Mode APIs, e.g. createRoot and useTransition. (available in experimental releases)

    • Compatibility solution for Flux-like libraries. (in progress @bvaughn, https://github.com/reactjs/rfcs/pull/147)

    • Changing the priority model to a more sensible one. (in progress @acdlite, https://github.com/facebook/react/pull/18612)

    • Only allow the last pending transition to finish. (in progress @acdlite)

    • Offscreen API (in progress @lunaruan)

    • Fire effects when hiding/showing content for Suspense

    • Fire effects when hiding/showing content for Offscreen

    • Show and hide Portal children when needed

    • Align with the ongoing standardization work for Scheduling (not started)

    • Fix major known bugs (in progress @gaearon and @acdlite)

    • Change event semantics. (in progress @sebmarkbage @trueadm)

    • Delegate to roots instead of document to enable more gradual adoption (in progress, @trueadm)

    • Flush discrete events in capture phase.

    • Consider getting default priority from event to play nicer with imperative code.

    • Finalize other API semantics and defaults. (not started)

    • Update typings and documentation.

  • Suspense for Data Fetching

    • Low-level support for signaling a component isn't ready to render (technically available in stable React too, but this API isn't considered stable).

    • Server renderer immediately flushes Suspense fallbacks (available in experimental releases)

    • A solution for GraphQL use cases (Relay Hooks, shipped).

    • A solution for non-GraphQL use cases (in progress @sebmarkbage in collaboration with Next.js).

    • A bundler integration for data-driven dependencies. (in progress)

    • Finalize Blocks API, including context.

    • A generic caching solution. (not started)

    • Some kind of a router integration.

It almost feels like "It's in production at Facebook, so we're done".

I can see how it could look this way, although tbh this reading is a bit demoralizing. :-) We've been working non-stop on this for all the past months, and many of the technical aspects are either finished or close to being finished. Most of the remaining works falls into two categories:

  • Fixing the flaws we discovered in the initial designs before we cement them in a stable release. If we were to release what we have now, we'd have to follow through with significant breaking changes in a few months. That would only be confusing.

  • Ecosystem compatibility and good defaults. It doesn't help if we release something that nobody can use today because it doesn't work with their libraries or existing approaches. So the bulk of the work (e.g. compatibility with Flux-like libraries via useMutableSource, picking better event semantics, shipping a recommended caching strategy) is in ensuring you would actually be able to use what we released. This is a long tail.

In terms of "can you use today"... Technically, you can use all of this today. We do. Specifically, we use Relay Hooks and Concurrent Mode. We still have significant planned changes and known issues, so the current state isn't meeting the bar where we would consider it ready for broad adoption. Of course, if you don't mind having bugs or APIs changing right under your hands, you're welcome to use the @experimental releases just like we do.

I wouldn't say Facebook is in a special position here in terms of us "being done". Quite the opposite, we're not done ā€” but internally, we're willing to tolerate churn and build on top of a moving train because that's how we know what we're building is solid. Without this heavy dogfooding, the flaws we discovered in six months would take several years to discover and redesign around.

To sum it up: there's more work to do.

@gaearon Thank you for this update! And I apologize if my tone was wrong. I admit I was a bit frustrated scouring Twitter for months and finding nothing. This aspect Server renderer immediately flushes Suspense fallbacks (available in experimental releases) looks like something I could allocate time on in testing this with Apollo Graphql for our implementation.

Yep, it should be ready for library authors and curious people to start playing with. The missing pieces are mostly about providing "happy paths" but most of the plumbing should be there.

Server renderer immediately flushes Suspense fallbacks (available in experimental releases)

Where can I read about this? I was hoping Concurrent Mode API Reference (Experimental) but no luck.

If anyone has a demo stitching Next.js, Relay Hooks and Concurrent Mode together (with SSR) that would be awesome. Otherwise I might just try my luck if I can find sufficient documentation.

@CrocoDillon

Thereā€™s no extra docs about SSR but itā€™s mostly because itā€™s just a new default behavior.

If you have an experimental release than any time a component suspends on the server, we flush the closest Suspense fallback instead. Then on the client you would use createRoot(node, { hydrate: true }).render(<App />).

Note this already enables all the new hydration features. So for example your Suspense boundaries will ā€œattachā€ to the server generated fallback HTML and then attempt to client render.

Also note that you can start hydrating before your whole app loads. When <App> has loaded, you can hydrate. As long as components below suspend when their code isnā€™t ready (similar to lazy). What React would do in this case is keep the server HTML content but ā€œattachā€ the Suspense boundary to it. When the child components load it will continue hydrating. The hydrated parts would become interactive and replay events.

You can probably ask @devknoll for Next integration attempts/examples. He probably has some.

You can enable concurrent mode in Next.js by installing react@experimental and react-dom@experimental and adding the following to next.config.js

// next.config.js
module.exports = {
  experimental: {
    reactMode: 'concurrent'
  }
}

Here's the Next.js discussion on this: https://github.com/zeit/next.js/discussions/10645

Is it possible to wait for any suspenses in server rendering (for cases such as static site gen for instance)? I agree that using callbacks is a good default, just wondering if it's overrideable?

Also note that you can start hydrating before your whole app loads. When has loaded, you can hydrate. As long as components below suspend when their code isnā€™t ready (similar to lazy). What React would do in this case is keep the server HTML content but ā€œattachā€ the Suspense boundary to it. When the child components load it will continue hydrating. The hydrated parts would become interactive and replay events.

@gaearon do you mean that you can render a component normally on the server, but use React.lazy on the client? Allowing you to return the full mark up from the server, but delay parsing and rendering of the component code on the client. The server rendered markup acts as the suspense fallback here?

@robrichard I haven't actually tried it with React.lazy specifically (we use a different wrapper at FB and Next also has its own version) but I expect that this is how it would work. Worth verifying :-) With certain limitations ā€” e.g. if you update its props and there's no memo bailout above, or if you update context above it, we'll have to remove it and show the fallback because we don't know what to do with it.

@gaearon what's the current state Partial Hydration? I know #14717 was merged but I doubt it made into any release?

It's been on in every @experimental release for a long time by now, as long as you use the unstable_createRoot API.

@gaearon are you able to please elaborate what you mean by "data driven dependencies"?

@gaearon are you able to please elaborate what you mean by "data driven dependencies"?

Yes, of course. I must apologize for the brevity of the list above ā€” it's very condensed and many of these are significant separate subprojects (which is why they're taking time).

Let me take a step back and give some broader context before answering your specific question. The broader context is that building a really good data fetching solution is really, really hard. Not just in the sense of the implementation but in the sense of the design. Typically, one would have to make a compromise between colocation (keeping the data requirements close to where the data is used) and efficiency (how early do we start loading the data, and can we avoid network waterfalls). This isn't as noticeable at a small scale, but as the number of components grows you really have to choose between great performance and easy-to-write code. In many cases, unfortunately, you don't get neither ā€” which is why data fetching in general is such a hot topic.

We have a very high bar for what gets into "official" React. To be "Reacty", it has to compose as well as regular React components do. This means we can't in good faith recommend a solution that we don't believe would scale to thousands of components. We also can't recommend a solution that forces you to write code in a convoluted optimized way to keep it performant. At FB, we've learned a lot of lessons in this from the Relay team. We know not everybody can use GraphQL, or would want to use Relay (it's not by itself very popular and the team hasn't optimized it for external adoption). But we want to make sure that our data fetching solution for general React incorporates the hard-earned lessons from Relay and doesn't suffer from having to choose between performance and colocation.

I want to emphasize this isn't just about big apps. The problems are most noticeable in big apps, but small apps that import a bunch of components from npm also suffer from a subset of these inefficiencies. Such as shipping too much client-side code and loading it in a waterfall. Or loading too much upfront. Also, small apps don't stay small apps forever. We want a solution that works great for an app of any size, just like the React component model works the same way regardless of your app's complexity.

Now, to address your specific question. Relay has a feature called "data-driven dependencies". One way to think about it is an evolution of dynamic import. Dynamic import is not always efficient. If you want to load some code only when a condition is true (e.g. "is user logged in" or "does user have unread messages"), your only option is to trigger it lazily. But this means you are "kicking off" fetching (such as with React.lazy) only when something is used. That's actually too late. For example, if you have several levels of code-split components (or components waiting for data), the innermost one would only start loading after the one above it has loaded. This is inefficient and is a network "waterfall". Relay "data-driven dependencies" let you specify the modules you want to fetch as a part of the query. I.e. "if some condition is true, include this code chunk with the data response". This lets you load all the code-split chunks you're going to need as early as possible. You don't have to trade colocation for performance. This might seem like not a big deal but it shaved off literal seconds in the product code.

Now, again, we're not putting Relay into React, and we don't want to force people to use GraphQL. But conceptually the feature itself is generic, and having a good open source solution for it would let people do a lot more code splitting than is done today (and thus ship a lot less client JS code!) That generic feature won't be called "data driven dependencies" ā€” that's just a Relay name I referred to. This feature will be a part of a larger recommended solution that doesn't require GraphQL. I just referred to it by that name in the list because this was a lot to explain for a single bullet list point.

I hope this clarifies it.

Was this page helpful?
0 / 5 - 0 ratings