-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to deal with suspend? #69
Comments
When I started implementation of this library there where no info when both hooks and suspense for data loading would be ready, but I thought that both of them could play nicely together. Right now we know that a stable release of React with hooks should be available soon and also it seems that the parts of suspense used by react-apollo-hooks can be considered as safe to use.
I think it's a quite common use case. You'd like to show a loading indicator if there is no all data or assets required for the current page already available. With suspense you can have the whole logic for that in a single place, e. g: const MyLazyComponent = React.lazy(() => import("./AComponent"));
function ComponentWithData() {
const { data } = useQuery(MY_QUERY);
return <MyLazyComponent data={data} />;
}
ReactDOM.render(
<Suspense
fallback={
<div>
{"GraphQL data or lazy bundle loading (I don't care whitch one)"}
</div>
}
>
<ComponentWithData />
</Suspense>
); Without suspense, you'd have to support loading states separately in both
Not necessary. You can put const FirstLazyComponent = React.lazy(() => import("./FirstLazyComponent"));
const SecondLazyComponent = React.lazy(() => import("./SecondLazyComponent"));
function ComponentWithData() {
const { data } = useQuery(MY_QUERY);
return <FirstLazyComponent data={data} />;
}
function App() {
return (
<Suspense
fallback={
<div>
This fallback will be shown during loading of GraphQL data or a bundle
for "FirstLazyComponent"
</div>
}
>
<ComponentWithData />
<div>
<Suspense
fallback={
<div>
This fallback will be shown during loading of a bundle for
"SecondLazyComponent"
</div>
}
>
<SecondLazyComponent />
</Suspense>
</div>
</Suspense>
);
}
ReactDOM.render(<App />); One thing to note. In order to avoid waterfall requests you have to do data preloading sometimes, e. g.: const MyLazyComponent = React.lazy(() => import("./AComponent"));
function ComponentWithData() {
// start preloading MyLazyComponent before suspending for GraphQL data
import("./AComponent").then(() => {
console.log("AComponent preloaded");
});
const { data } = useQuery(MY_QUERY);
return <MyLazyComponent data={data} />;
}
ReactDOM.render(
<Suspense
fallback={
<div>
{"GraphQL data or lazy bundle loading (I don't care whitch one)"}
</div>
}
>
<ComponentWithData />
</Suspense>
); Please let me know if my examples are clear. |
Thanks for the elaborate answer, but it's still confusing. It's exactly what I am fighting with. The component with I have been finding such contrived examples everywhere, but it just doesn't make sense. Let's consider a page displaying some list of items on the left side and the google map with markers related to that list. When filter for that list is altered, it will rerun both queries. There should be some loading indicator for TheList, but TheMap should be still visible and update its markers when data arrive. function TheMap() {
// this component should never disappear when loading
const { data } = useQuery(QUERY_FOR_MARKERS);
return <GoogleMap>{makeMarkers(data)}</GoogleMap>;
}
function TheList() {
const { data } = useQuery(QUERY_FOR_LIST);
return <>
<Filter />
<List items={data.items} />
</>
}
ReactDOM.render(
<Suspense fallback={<Spinner />}>
<TheList />
<TheMap />
</Suspense>
); Now I do realize I can render anything as a fallback, even the same components to display the same UI, but I would need to pass some <Suspense fallback={
<>
<TheListIsLoading />
<TheEmptyMap />
</>
}>
<TheList />
<TheMap />
</Suspense> Note that even this is bad because TheMap and TheEmptyMap are different component, React will throw away those DOM elements which will cause the whole google map refresh. It certainly is not pretty. |
FWIW lack of features like this is part of the reasons we don’t recommend anyone to start using Suspense for data fetching now. (The feature you’re asking for is essentially a render prop API for switching between fallback and main content. It’s planned but not ready yet.) It doesn’t hurt to experiment with it but I think we should be clear that Suspense data fetching story (and patterns around it essential to creating good UX) is not ready for prime time. |
Thanks for clarification @gaearon, that's what I thought :) @trojanowski Would you consider switching the suspend to false by default then? Are you honestly using it or switching it off everywhere as well? :) |
Any reason you don’t want to wrap I’m using it exactly in such way, I have some main spinners in the form of a fallback on certain positions in the tree and then depending on if multiple query components on a page should render independently of the loading state of another query I isolate them with another fallback. It works the same as with error boundaries which made it (personally) easy to work with. |
@rovansteen Sorry, I am not sure what you mean, can you show some example? I am failing to see how is it at the end different from loading prop which lets you decide how the component should look like without data. In the case of the map, I know that I need to keep the same component in the tree, otherwise the nasty effect of reloading it will happen. So there needs to be a prop that will decide. |
Thank you for your comment @gaearon. BTW do you think it's still possible that Suspense for data fetching will be available mid-2019?
@FredyC Yes, now I think it makes sense. It would allow us to mark this library as production-ready (the suspense part would be still experimental). We'd eventually switch it back when Suspense for data fetching is stable (these changes are the part I like the least). What do you think @umidbekkarimov @sijad @seeden @dimitar-nikovski?
I'm using it for an internal app, and it works for me.
I was thinking about the same. I guess @rovansteen meant something like this: ReactDOM.render(
<>
<Suspense fallback={<TheListIsLoading />}>
<TheList />
</Suspense>
<Suspense fallback={<TheMapIsLoading />}>
<TheMap />
</Suspense>
</>
); |
@FredyC I think I get what you mean about the fallback completely yanking/destroying the inner component and causing a full new component mount any time suspense kicks in. I'm in a similar place not sure how to handle that without suspend false. The issue I have when using suspend false is the whole hooks-in-order thing. I'm still unclear if its ok to return early and not call other hooks in the case when data.loading, data.error, etc. On one hand ensuring the "order" is easy, but in the case of returning early its possible to not invoke every single hook in the component - i.e. 1, next time 1,2,3. I'm not sure if that is ok or not. I guess the point about Suspense is you are not limited to one - they can be as specific as you wish, so it only effects the granularity you want:
|
@trojanowski & @joenoon Yea, your examples are kinda what I was afraid of. How is that actually better I wonder? I mean if there would be two components
I am glad you agree. I was already thinking about forking and doing it by myself because it's starting to be rather annoying :)
Wish you were able to share at least some part of it because I cannot imagine how is that possible really :) |
In your particular use case it might not benefit you much but imagine a dashboard with a dozen cards/panels that each query their own data. Without suspense every query had to handle their own loading state. With suspense you can just wrap them all in one fallback and you can show one spinner for the whole dashboard until every card has its data. |
If you have a hard time understanding the benefits of Suspense I recommend watching this video from two members of the core team of React showing what you can do now and in the future with Suspense: https://youtu.be/ByBPyMBTzM0 |
Currently I'm writing SSR heavy app with dashboard, so I can not utilize suspense much, I never gave it a real shot.
Let's disable it by for default, give it a time, and eventually some one will came up with good api for production apps. PS: This is how i'm using export function useAppQuery(query, options) {
return useQuery(query, {
suspend: false,
errorPolicy: "all",
fetchPolicy: "cache-and-network",
...options,
});
} |
@rovansteen
You know, I am not totally convinced this is a good thing. I mean sure, dozens of loaders is bad. but throwing away everything and showing one loader just because a single card needs to refresh its data? How that can be a good UX really? And it's like you cannot even opt-out of Suspense conditionally. For example it would makes sense to have a single loader initially, but later when you are fetching data within a single card, there should be a loader for that one and others should stay there, right?
I've seen those videos before, but I'll rewatch in case I've missed something there. |
@rovansteen I have rewatched the video and as much as nice that demo feels, it's apparent it was built for that particular example. I still cannot imagine I could utilize that in my use case where I simply cannot afford to remount the map component. |
@FredyC The example in the video is quite common and I could think of at least a dozen more examples where suspense would greatly help. I don't understand why there would a be remount if you implement it as @joenoon explained. Sure, that might not benefit you much compared to just checking the |
@rovansteen Strange, the app we are making for the last year seem pretty regular and yet I am not finding much of the resemblance with that demo there. Most likely it is some mindset issue for sure. My main concern at this point is probably about that map component. I would have to do this to avoid UI hiccup. Is it worth the hassle? Not really... <Suspense fallback={<TheMap loading={true} />}>
<TheMap />
</Suspense> I am only hoping that React will bring some other way in the future to tackle these scenarios more gracefully otherwise it's hard to imagine going full Suspense. |
@FredyC, first of all, I'm not saying your app isn't regular if you don't have a use case for Suspense. I'm saying that Suspense solves a lot of challenges for interfaces that deal with async data. Second, why the binary thinking? Suspense is just a tool that allows you to work with async data in a more sync way. In your map example, I could imagine you are just fetching data to plot on your map, so you never want your map to re-render when the status of your data fetching changes. What do you expect from React to improve for your use-case? |
Yes, in your example it isn't better. However, it is in many other cases (like the most I was working on). Let's say you have an app with many routes and each route needs some GraphQL data. Thanks to suspense it's enough to have loading state support just in one place instead of in each of those routes separately, e. g.: function App() {
return (
<Layout>
<Suspense fallback={<Loading />}>
<Switch>
<Route component={LogInPage} exact path="/login" />
<Route component={SignUpPage} exact path="/signup" />
<Route component={PostPage} exact path="/posts/:postId" />
<Route component={UserPage} exact path="/@:username" />
<Route component={ExplorePage} exact path="/explore" />
<Route component={HomePage} exact path="/" />
</Switch>
</Suspense>
</Layout>
);
}
I still think that
You can look at the port of Pupstagram sample app: https://codesandbox.io/s/8819w85jn9. |
Curious I haven't thought about that, such an obvious and easy solution, duh! 👍 There is also another project worth investigating: https://github.com/Urigo/WhatsApp-Clone-Client-React. Although briefly looking at it there is just Yea I think I will give it a rest, for now, and enjoy released hooks and return to this when it gets more attention. Feel free to close. |
I ran into problems too. Using suspend (and especially as a default value) is very dangerous. Here is the reason from the react authors: |
FWIW, |
As I mentioned earlier, the feature you’re asking for is planned but it’s not available yet. |
Hi Dan :), Users using this hooks library (with suspense default on) will run into these problems. (Like me) We should wait for the react team to officially release the suspense with fetch feature and then activate it as default. Because only then the ui toolkit maintainers have a chance to react to their library not working with features from react version x and can adapt the supported react versions until they migrated away from findDOMNode. |
The |
I just published version 0.4.0 which doesn't use Suspense by default. I'm going to close this issue and return to the discussion when Suspense for data fetching is stable. |
@trojanowski is there a way to turn on the suspense globally or do I always have to pass it to |
@capaj You can use the same trick mentioned here, but the opposite: #69 (comment) |
Just wanted to say this was a helpful, high-quality conversation and I appreciate the change (@trojanowski) to make Suspense opt-in. It lowers the barriers to entry and will allow more people to start using this library with far less trouble and confusion. 👍 |
is there a way to use suspend with the official apollo react hooks implementation? |
Any updates on this? |
@heiwais25 You should probably watch official react-apollo, this project was superseded by it some time ago. |
I am mostly curious what was the initial motivation to have
suspend = true
by default already. There is a lack of information on how to actually use suspense for data fetching considering that it's planned for late 2019.I just got burned by this badly when I forgot to set it false for one component deep down in the tree. I do have a
Suspense
way up in a tree to handleReact.lazy
. Suddenly when a query was rerun due to variables changing, everything got unmounted and fallback to top-level Suspense. It took me a while and some debugging to realize the culprit.Do you have some actual examples of how to integrate it with react-apollo-hooks? Is it someone using it successfully already or is it everyone switching it off?
Let's consider a pretty simple scenario. A component that has
useQuery
and renders some markup with data from that query. Now withloading
prop the decision what to show is done deeper in the tree. However, with suspension in play, it has to be done at top levels? I just cannot wrap my head around this for some reason.The text was updated successfully, but these errors were encountered: