relay-tools/relay-hooks

Differences with upcoming Relay Hooks in react-relay

jstejada opened this issue ยท 14 comments

This is an issue to track the differences between the apis exposed in this package and the ones we will expose as part of Relay's react-relay package. Hopefully, this can help the community decide on whether they're safe to adopt and the cost of migration, as well as inform potential changes to the api.

useQuery

  • useQuery will no longer take environment as an argument. Instead it reads the environment set in context; this also implies that it no longer sets any React context.
    • Since queries with useQuery no longer set context, we will expose a new RelayEnvironmentProvider component that takes an environment and sets it in context; variables will no longer be part of context. A RelayEnvironmentProvider should be rendered once at the root of the app, and multiple useQuery's can be rendered under this environment provider.
  • useQuery will now take a fetchPolicy as part of a 3rd configuration argument, to determine whether it should use data cached in the Relay store and whether to send a network request. The options are:
    • store-or-network (default): Reuse data cached in the store; if the whole query is cached, skip the network request
    • store-and-network: Reuse data cached in the store; always send a network request.
    • network-only: Don't reuse data cached in the store; always send a network request. (This is the default behavior of Relay's existing QueryRenderer.
    • store-only: Reuse data cached in the store; never send a network request.
  • useQuery returns a single data object with the query's data, and nothing else.
  • useQuery is integrated with Suspense for rendering loading states. This means that depending on the fetchPolicy, if the query can't render due to missing data (i.e. if the data isn't cached), the query will suspend until the network request completes, and the loading state should be rendered with a Suspense boundary around the query.
  • useQuery will throw an error if an error occurs, which should be caught by a React Error Boundary around the query.

useFragment

  • useFragment will no longer take a "fragment spec" (i.e. a map of keys to fragment definitions). Instead, it will always take a single fragment definition as the first argument, and a fragment reference as the second argument. If multiple fragments need to be used in a single component, useFragment can be used multiple times.
  • useFragment takes a fragment definition and a fragment ref, and returns the data for that fragment. Fragment refs are internal Relay objects that represent pointers to specific instances of the data; e.g. if we have a fragment on the User type, the fragment ref specifies which User, for example User with id 4. The fragment ref is not a new concept; it is the same prop that existing RelayModern containers currently take, only that it is explicitly exchanged for the data inside the component via calling useFragment, instead of that being hidden in the HOC code.
  • useFragment also subscribes to changes in the data, like the HOC version.
  • useFragment is also integrated with Suspense, meaning that if it has any missing data it will suspend until it's parent query completes. This enables rendering of data that is partially cached in the Relay store, or data that has been @defer'd.

Pagination and Refetch

Pagination and Refetch will have completely separate Hooks APIs (one for pagination, one for refetch), and also integrated with Suspense. They will be quite different from the apis exposed here as configuration to useFragment, so the migration cost might be higher if the api from this repo is adopted.

Mutations and Susbcriptions

Mutations and Subscriptions will also have completely separate Hooks APIs, and will also integrated with Suspense; the details of what the right APIs with Suspense will be are still under consideration.

Backwards Compatibility

Everything will be released in a backwards compatible way, meaning that new Relay Hooks will work with existing containers. Note that as of Relay v5.0.0 (will be released in the coming days), our existing RelayModern containers will no longer consume variables from context, but instead consume variables via fragment ownership. As mentioned before, Relay context will no longer contain variables; only an environment.

Excellent issue.

This project does not want to be a substitute for react-relay. The initial purpose of the project was to imagine its own relay version with hooks and to propose features to be integrated into the facebook/relay repository.

At the moment there is not a version of the official hooks available otherwise there would be a dependency with the original version and I would have added new features, not modifying the existing ones.

The goal is to help make relay better and easier to use.

Now, thanks to this information, I will be able to make relay-hooks more similar to the one that will be distributed in react-relay so those who want can get familiar with the hooks version.

Thanks @morrys! Oh yeah definitely, wasn't implying that it was. This is great, and we'd also love to hear your feedback when we make the new apis public. Mostly just wanted to document the main differences with the upcoming apis so the community could be aware.

Thanks!!

I knew relay was working on a hooks api and the reason why I'm using this project is to make migration less painful when the relay is shipped with official hooks support. It's good to see morrys is trying to mimic the coming official api as closely as possible and this info is certainly helpful for his efforts.

leops commented

Regarding the integration of Suspense, we've been experimenting ways of using Relay through hooks at my company for about 6 months now, and the API we ended up with has been open-sourced at https://github.com/levels3d/offblast
TL;DR we found that suspending in the useQuery hook is usually an anti-pattern with GraphQL as we tends to query all the data needed by the many components of a page in a single query (and so a single useQuery call) near the root of the React tree of the page. Suspending in this place means showing a single loading indicator / fallback content for almost the entire viewport, something we found undesirable as we would rather render most of the layout of the page and only show placeholders in the components that actually use the fetched data.
Additionally, we found it useful to add an additional "skip" behavior to the fetchPolicy argument for cases were we want to skip the query entirely (since you can't put a hook in a condition).

@leops do you have a relay-example version using hooks + suspense?

leops commented

@sibelius Not yet, though I don't think it would take me much time to publish a fork

open sourced here facebook/relay@b83aace

@morrys @sibelius @renanmav Is there any plan to support subscriptions via hooks?

EDIT: I would be more than happy to contribute the useSubscription that follows the upstream version. (https://github.com/facebook/relay/blob/e2d5e5c4630931f35495d105353594b42babc818/packages/relay-experimental/useSubscription.js)

@stramel, @rbalicki2 added support for useSubscription internally and it will be included in the next react-relay@experimental release

@jstejada, @rbalicki2 In the PR #90 there is a different implementation and use of usePreloadedQuery.

Being in Relay still in the experimental phase I would like your opinion about it.

Is the intention to deprecate this library once Relay's hooks implementation is no longer "experimental"?

My biggest concern is that Relay's implementation relies on Suspense, which currently doesn't support SSR.

It all depends on how relay-experimental it will be and surely the repository will be supported as long as it is used.

Is the idea with the experimental official useQuery that it it will suspend for loading and throw an error (to be caught by an error boundary for the error state, rather than the {error, props} (and loading == !props) API?
This seems like the hardest thing to migrate because it significantly changes the component composition pattern you need to use.

useQuery returns a single data object with the query's data, and nothing else.

// relay-hooks
const {props, error, retry, cached} = useQuery(query, variables, options);
// relay
const data = useQuery(query, variables, options);

Just as an FYI for anyone following this issue, we've released Relay Hooks in v11 of Relay! https://github.com/facebook/relay/releases/tag/v11.0.0