Refetching and Loading More Data
Recommended background reading
- Queries and mutations in GraphQL
- A Guided Tour of Relay: Re-Rendering Fragments with Different Data
- React documentation: Suspense for Data Fetching
Refetching and Loading More Data
Some of the following features comes with some constraints if your GraphQL server schema does not follow the Relay specification. Read more about using RescriptRelay with schemas that don't conform to the Relay specification here.
Sometimes you'll want to refresh or refetch data in specific parts of your views without re-issuing the full query the data was originally rendered from. Relay makes this very simple if your schema conforms to the Relay specification and implements the Node
interface, or if your fragment is on the Query
or Viewer
type. This page will focus on Relays feature to refetch a fragment.
Making a fragment refetchable
You can make a fragment refetchable by adding the @refetchable(queryName: "<queryName>")
directive to it. Let's look at an example of making a fragment refetchable and refetching it with. Here's a component showing some information about a user, and then rendering a "Show bio"-button to refetch the fragment it uses, but include more information about the user:
/* UserProfileHeader.res */
module UserFragment = %relay(
`
fragment UserProfileHeader_user on User
@refetchable(queryName: "UserProfileHeaderRefetchQuery")
@argumentDefinitions(
includeFullBio: {type: "Boolean", defaultValue: false}
bioMaxLength: {type: "Int"}
) {
firstName
lastName
bio(maxLength: $bioMaxLength) @include(if: $includeFullBio) {
presentationText
age
}
}
`
)
@react.component
let make = (~user) => {
let (user, refetch) = UserFragment.useRefetchable(user)
<>
<div> {React.string(user.firstName ++ (" " ++ user.lastName))} </div>
{switch user.bio {
| Some(bio) => <>
<div> {React.string(bio.presentationText)} </div>
<button
type_="button"
onClick={_ =>
refetch(~variables=UserFragment.makeRefetchVariables(~bioMaxLength=Some(500)))}>
{React.string("Show full bio text")}
</button>
<div> {React.string("Age: " ++ string_of_int(bio.age))} </div>
</>
| None =>
<button
type_="button"
onClick={_ =>
refetch(~variables=UserFragment.makeRefetchVariables(~includeFullBio=Some(true)))}>
{React.string("Show bio")}
</button>
}}
</>
}
Whew, new stuff to break down. Let's start from the top:
- We've added two directives to our fragment,
@refetchable
and@argumentDefinitions
.@refetchable
tells Relay that this fragment can be refetched, and also tells the Relay compiler to automatically generate a query to use when refetching the fragment. Remember, fragments always have to end up in a query to be usable, so you need a query to refetch a fragment. But, Relay can autogenerate the query for your, so the only thing you need to think about is giving@refetchable
aqueryName
prop with a name for your refetch query. @argumentDefinitions
, is a way to define that a fragment can take arguments, much like props in React. This is a very neat feature and you're encouraged to use it as much as you can when it makes sense. Basically, we're saying that "this fragment needs variables$includeFullBio
(boolean) and$bioMaxLength
(int). However, if the one who spreads this fragment does not pass any of those variables, they should have default values". So, for example,$includeFullBio
is how we'll control if we should fetch the extra data or not, and since we don't want to load the extra data before the user explicitly asks for it, we'll make sure it defaults tofalse
.You're encouraged to read more about@argumentDefinitions
here.- In our selection, we use
$includeFullBio
via the built-in GraphQL directive@include
to control whether thebio
field should be included in the query or not. Read more about GraphQL directives and the built in directives@include
and@skip
here. - Whenever a fragment has a
@refetchable
directive on it with aqueryName
, RescriptRelay will autogenerate auseRefetchable
React hook that you can use, in addition to the defaultuse
hook.useRefetchable
works just likeuse
, only that it returns a tuple containing both the data and a function to refetch the fragment instead of just the data. - When rendering, we check for the
bio
object in the data. If it's not there, we render a button to refetch the fragment with$includeFullBio
set totrue
. Otherwise, we render a simple UI for showing the full bio. - Note the use of the function
UserFragment.makeRefetchVariables
to make the refetch variables. The reason we're not passing raw variables just like we'd normally do is that when making a refetch, all variables are optional. Relay lets you supply only the variables you want to change from the last fetch of the fragment, and it will re-use anything you don't pass to it from the last fetch. So, if you simply doUserFragment.makeRefetchVariables()
without passing any changed variables, the fragment will be refetched with the same configuration it was fetched before.makeRefetchVariables
is also autogenerated by RescriptRelay whenever there's a@refetchable
directive on the fragment, and its a helper to let you easily provide only the parts of the variables that has changed.
makeRefetchVariables
Let's dive into makeRefetchVariables
, because this can be a bit tricky to understand. Remember that makeRefetchVariables
takes a diff of the new variables you want, based off of the last set of variables used to fetch the fragment data. Let's take a concrete example:
In the example above we have variables $includeFullBio
and $bioMaxLength
. Let's say this fragment gets rendered with $includeFullBio = true
and $bioMaxLength
not set yet.
- If I want to refetch the fragment with
$includeFullBio
changed tofalse
, I'd domakeRefetchVariables(~includeFullBio=Some(false))
.Some(false)
here tells Relay that "I want to change$includeFullBio
in the refetch tofalse
. Use the last value for all other variables". - If I want to set
$bioMaxLength
to something, I'd domakeRefetchVariables(~bioMaxLength=Some(200))
. This tells Relay "setbioMaxLength
, leave everything else as is". - If I have
bioMaxLength
set and I want to refetch data with it not set at all (equivalent of passingnull
), I'd domakeRefetchVariables(~bioMaxLength=None)
.
So, notice how Some(value)
means "set this value", None
means "unset this value", and leaving out the variable all together from makeRefetchVariables
means "don't change this variable, reuse the last value".
This allows us to refetch data in response to parameters changing, without having to keep track of all the current variable values, if we know they haven't changed. Pretty neat!
Conditional fragments
Conditionally including entire fragments is easy. Please refer to the @alias
fragment spread directive.
Summing up
- Defining a fragment as
@refetchable
will give you auseRefetchable
hook, which in turn gives you arefetch
function that you can use to refetch the fragment. - When refetching, you provide only the parts of
variables
that you want to change. This means that providing an emptyFragment.makeRefetchVariables()
will refetch the fragment with the same configuration as it was last fetched with. - Control what's fetched at what point with GraphQL variables and
@argumentDefinitions
, as examplified above.
Wrapping up
That's how you refetch a fragment. This can be used to implement a number of patterns like polling, "Read more"-functionality, deferring loading certain expensive data until needed, and so on. A tool worthy of your toolbox!
API Reference
Adding @refetchable
to a fragment will make RescriptRelay add a useRefetchable
hook to your fragment. That useRefetchable
hook takes the same parameter as the regular use
hook on a fragment, an object containing a fragment reference for this particular fragment.
It returns (fragmentData, refetchFunction)
, where fragmentData
is the data for the fragment.
refetchFunction
The refetch function that useRefetchable
returns take the same arguments as the use
hook on a query with one important difference: the properties of the variables
object will all be optional. This is because you only need to provide what properties in the variables you want to change from the last fetch when refetching. RescriptRelay autogenerates a makeRefetchVariables
for you function that simplifies this.
makeRefetchVariables
makeRefetchVariables
is autogenerated and available on your fragment when you've defined your fragment as @refetchable
. It's a helper function to make it easy to build your refetch variables by only providing what you want to change.