https://reactjs.org/docs/concurrent-mode-suspense.html
I understand it's still experimental and rough around the edges, but I feel obliged to open discussion.
The misleading part is mostly about utilizing a "global" variable outside of the component scope in Suspense related examples. In the real app that can hardly work out because data loading usually needs to happen based on some input, eg. from props.
It's nice that it works with Relay differently, but for people who don't use Relay, this guide doesn't help that much. On the contrary, it might cause more confusion as it certainly has caused me 🤕
Perhaps it might be a better option at this phase to link there relevant parts of Relay code to get more hands-on information for interested people.
This is not a "global" variable, it's declared in a top scope. This is to demonstrate you need to start fetching as early as possible.
The guide does include plenty of examples when fetching is done in response to user action. For example, here (sandbox). We still fetch the first value early, but then we fetch the next ones on click. A router or an opinionated library could enforce a similar guarantee with a different mechanism.
for people who don't use Relay, this guide doesn't help that much
This guide is aimed at people implementing libraries. I know it's a bit confusing because it's not how most fetching works in React ecosystem today. That is a big problem because the currently popular solutions are inefficient. So we're explaining it from the first principles ("fetch as early as you can") and will provide more guidance with time to show how that can be done in practice. Stay tuned!
This is to demonstrate you need to start fetching as early as possible.
I get that, but those examples are giving the impression that you have to know input data in top scope as well. Even in your linked example fetching for ID = 0 is happening without direct user input. What if I want to initiate the fetching based on prop value and not sooner?
It's totally possible I am simply misunderstanding the concepts, but if that guide is supposed to make the concept clear-ish, then I am filing feedback here that it does not clear things for me :)
Even in your linked example fetching for ID = 0 is happening without direct user input.
If you did in based on initial route params it could still happen in the top level.
What if I want to initiate the fetching based on prop value and not sooner?
That’s kind of the whole thing we’re arguing against. If you fetch based on props, you are fetching too late. I understand these solutions are mainstream but we want to encourage the next wave of solutions to make prefetching the default pattern instead. This means you don’t fetch based on props at all — you fetch based on initial route, and later you fetch based on interactions.
We’re aware this difference is subtle. There will be more information coming in form of blog posts, etc.
Hope this helps: https://mobile.twitter.com/dan_abramov/status/1191569529471549440
I get that, but those examples are giving the impression that you have to know input data in top scope as well. Even in your linked example fetching for ID = 0 is happening without direct user input. What if I want to initiate the fetching based on prop value and not sooner?
Most of the time the prop you want to fetch data based in (like a param) will be available in the routing context. So a router could look like this:
<Route
path="user/:id"
preload={({ id }) => fetchUser(id)}
>
<UserPage />
</Route>
It shifts the responsibility for the <UserPage /> from "show me the page for this user id" to "show me the page for the data I'm passing you".
The discussion about implementing Suspense in react-router might be interesting to follow.
Alright, I think I am starting to get it. Thank you @rovansteen for clearing it up and for a link to the discussion 💯
I think this is a very important piece of information that's missing from the guide. I haven't really seen any hint there about this assumption for input data coming from routes. Perhaps even include that link to a react-router discussion.
I am trying to implement a basic layer around Firebase products with Suspense in mind, namely Firestore Cloud which I seem to have worked out. It is a custom hook because it's accessing the Context with the cache object, but it works nicely so far.
const account = useFirestore<TAccount>(db =>
db.collection(collections.accounts).doc(accountId),
)
But there is also Authentication I want to cover which is based on an imperative event listener. It doesn't feel right "hiding" something like that inside the data fetching layer.
React.useEffect(() => auth.onAuthStateChanged(userRecord => {
// userRecord.uid should be used to load a user profile from DB
// userRecord might be null when unauthenticated
}), [auth])
const user = useFirestore<TUser>(db =>
db.collection(collections.users).doc(userRecord.uid), // <- Not sure how to wait for uid
)
For now, I have resolved it in a very dirty way, I am creating a Promise that is fulfilled based on that even listener and passing that down to another component that does the suspension. It works, but I have a feeling there is a better way.
export const SuspendMe: React.FC<TProps> = ({ promise, children }) => {
if (promise) {
throw promise
}
return <>{children}</>
}
Either way, I mostly wanted to show that expecting route-based data fetching is not always feasible. It would be nice if the guide could show some of these edge case scenarios as well.
Most helpful comment
If you did in based on initial route params it could still happen in the top level.
That’s kind of the whole thing we’re arguing against. If you fetch based on props, you are fetching too late. I understand these solutions are mainstream but we want to encourage the next wave of solutions to make prefetching the default pattern instead. This means you don’t fetch based on props at all — you fetch based on initial route, and later you fetch based on interactions.
We’re aware this difference is subtle. There will be more information coming in form of blog posts, etc.