Skip to content
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

Suspense support for React hooks #215

Merged
merged 1 commit into from
Jun 30, 2023
Merged

Suspense support for React hooks #215

merged 1 commit into from
Jun 30, 2023

Conversation

airhorns
Copy link
Contributor

@airhorns airhorns commented Jun 24, 2023

React's new Suspense support is the perfect thing for components declaring that they need something to happen before they can fully render. With all the upcoming auth work that we are doing, I think we're going to have lots of situations on the frontend where we want to know current login state, but where we don't have it yet because its a GraphQL call away.

We could solve this by wrapping the whole app in a thing that doesn't render until the session has been fetched. Thats what the <ClerkProvider/> does. I think we could do the same thing in our provider, but that'd require us assuming that every provider wants a session, which I don't think is a great assumption.

Instead, I'd rather have the components that need to know login state block, but the ones that don't ... not! We can do that with suspense. If we add a <RequireLogin/> component, it can access session state with a suspense, and then when it arrives, actually decide what to do. It would use the normal, community-agreed-upon mechanism for showing intermediate state spinners or what have you as well. I think our app template should have a base-level suspense that shows a big spinner, but that's nice cause users can customize it or add suspense catchers farther down the tree.

So, this adds suspense support to our React hooks! You can see it as an alternative to the fetching boolean. Instead of managing the fetching state, you punt it to your parent. See the tests for invocation examples.

One annoyance with the way urql's suspense support works is that you have to turn suspense support on at the whole client level, and then individual hooks can choose to enable it or not. By default in urql, when you turn on suspense support in the client, all hooks start using it, and I don't think we want that, we just want folks to be able to use it if they want to. So, there's a bit of args munging to have suspense on at the client level so urql will do it at all, and then we opt-in on a per-hook basis.

@airhorns airhorns force-pushed the urql-suspense branch 2 times, most recently from 3f0ceba to 99c6658 Compare June 24, 2023 03:29
@airhorns airhorns changed the base branch from main to urql-4 June 24, 2023 03:29
@airhorns airhorns requested a review from jasong689 June 24, 2023 03:29
@airhorns airhorns marked this pull request as ready for review June 24, 2023 03:29
@airhorns airhorns requested a review from kristianpd June 24, 2023 03:30
@airhorns airhorns changed the title urql suspense suspense support for react hooks Jun 24, 2023
@airhorns airhorns force-pushed the urql-suspense branch 2 times, most recently from 6da171f to 22fe891 Compare June 24, 2023 03:46
@airhorns airhorns changed the title suspense support for react hooks Suspense support for React hooks Jun 24, 2023
Base automatically changed from urql-4 to main June 26, 2023 15:47
@airhorns
Copy link
Contributor Author

ping @kristianpd @jasong689

// use a memo as urql rerenders on context identity changes
const context = useMemo(() => {
return {
suspense: !!options?.suspense,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm guessing this keeps suspense off by default for all hooks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct!

packages/blog-example/src/App.tsx Outdated Show resolved Hide resolved
React's new Suspense support is the perfect thing for components declaring that they need something to happen before they can fully render. With all the upcoming auth work that we are doing, I think we're going to have lots of situations on the frontend where we want to know current login state, but where we don't have it yet because its a GraphQL call away.

We could solve this by wrapping the whole app in a thing that doesn't render until the session has been fetched. Thats what the `<ClerkProvider/>` does. I think we could do the same thing in our provider, but that'd require us assuming that every provider wants a session, which I don't think is a great assumption.

Instead, I'd rather have the components that need to know login state block, but the ones that don't ... not! We can do that with suspense. If we add a `<RequireLogin/>` component, it can access session state with a suspense, and then when it arrives, actually decide what to do. It would use the normal, community-agreed-upon mechanism for showing intermediate state spinners or what have you as well. I think our app template should have a base-level suspense that shows a big spinner, but that's nice cause users can customize it or add suspense catchers farther down the tree.

So, this adds suspense support to our React hooks! You can see it as an alternative to the `fetching` boolean. Instead of managing the fetching state, you punt it to your parent. See the tests for invocation examples.

One annoyance with the way urql's suspense support works is that you have to turn suspense support on at the whole client level, and then individual hooks can choose to enable it or not. By default in urql, when you turn on suspense support in the client, all hooks start using it, and I don't think we want that, we just want folks to be able to use it if they want to. So, there's a bit of args munging to have suspense on at the client level so urql will do it at all, and then we opt-in on a per-hook basis.
@airhorns airhorns merged commit d59d082 into main Jun 30, 2023
@airhorns airhorns deleted the urql-suspense branch June 30, 2023 15:23
airhorns added a commit that referenced this pull request Jul 6, 2023
… suspend by default

In #215, we introduced automatic suspense support for the React query hooks, which allows developers (and us) to mark specific queries with "we want them to suspend" so that urql can do its thing under the hood and do so. We wanted this for a nice api in the `useSession` etc hooks.

To implement this though, we need to turn urql's suspense support on at the client level. This is a constructor option which makes suspense available at all *and* makes it the default for all queries. So that this wasn't a breaking change for all users, I turned on suspense support in the client, but then made all queries pass an explicit `suspense: false` down to urql so the behaviour was unchanged between versions. If a query wants to suspend, it has to opt-in by passing `suspense: true`.

I bungled this though, and did it for all the high level hooks like `useFindMany` and `useAction`, but I forgot that we export and make use of the low level `useGadgetQuery` hook for running manual GraphQL queries. Critically, we use this in the `@gadgetinc/react-shopify-app-bridge` package to detect installed status. The effect of this miss is that when the old react-shopify-app-bridge package is paired with a new api-client-core version, the bridge gets a client marked with `suspense: true` on the constructor, and then uses the low level `useGadgetQuery` hook, which doesn't apply the per-query default. The hook then suspends, which we learned breaks shopify installation.
airhorns added a commit that referenced this pull request Jul 6, 2023
…r react hooks where we know its supported

This was a bad approach from #215. If we ship suspense on by default in api-client-core, it will be force-upgraded by Gadget regenerating packages on users *without* a react dependency bump at the same time, which means folks' clients will start suspending by default when used with old react hook versions. Oops -- thats a big breaking change that users don't have control over.

Instead, we only have new versions of the react hooks turn on suspense mode by hacking it in manually.
airhorns added a commit that referenced this pull request Jul 6, 2023
…r react hooks where we know its supported

This was a bad approach from #215. If we ship suspense on by default in api-client-core, it will be force-upgraded by Gadget regenerating packages on users *without* a react dependency bump at the same time, which means folks' clients will start suspending by default when used with old react hook versions. Oops -- thats a big breaking change that users don't have control over.

Instead, we only have new versions of the react hooks turn on suspense mode by hacking it in manually.
airhorns added a commit that referenced this pull request Jul 6, 2023
… suspend by default

In #215, we introduced automatic suspense support for the React query hooks, which allows developers (and us) to mark specific queries with "we want them to suspend" so that urql can do its thing under the hood and do so. We wanted this for a nice api in the `useSession` etc hooks.

To implement this though, we need to turn urql's suspense support on at the client level. This is a constructor option which makes suspense available at all *and* makes it the default for all queries. So that this wasn't a breaking change for all users, I turned on suspense support in the client, but then made all queries pass an explicit `suspense: false` down to urql so the behaviour was unchanged between versions. If a query wants to suspend, it has to opt-in by passing `suspense: true`.

I bungled this though, and did it for all the high level hooks like `useFindMany` and `useAction`, but I forgot that we export and make use of the low level `useGadgetQuery` hook for running manual GraphQL queries. Critically, we use this in the `@gadgetinc/react-shopify-app-bridge` package to detect installed status. The effect of this miss is that when the old react-shopify-app-bridge package is paired with a new api-client-core version, the bridge gets a client marked with `suspense: true` on the constructor, and then uses the low level `useGadgetQuery` hook, which doesn't apply the per-query default. The hook then suspends, which we learned breaks shopify installation.
airhorns added a commit that referenced this pull request Jul 6, 2023
…r react hooks where we know its supported

This was a bad approach from #215. If we ship suspense on by default in api-client-core, it will be force-upgraded by Gadget regenerating packages on users *without* a react dependency bump at the same time, which means folks' clients will start suspending by default when used with old react hook versions. Oops -- thats a big breaking change that users don't have control over.

Instead, we only have new versions of the react hooks turn on suspense mode by hacking it in manually.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants