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,
@refetchableand@argumentDefinitions.@refetchabletells 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@refetchableaqueryNameprop 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,$includeFullBiois 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@argumentDefinitionshere.- In our selection, we use
$includeFullBiovia the built-in GraphQL directive@includeto control whether thebiofield should be included in the query or not. Read more about GraphQL directives and the built in directives@includeand@skiphere. - Whenever a fragment has a
@refetchabledirective on it with aqueryName, RescriptRelay will autogenerate auseRefetchableReact hook that you can use, in addition to the defaultusehook.useRefetchableworks 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
bioobject in the data. If it's not there, we render a button to refetch the fragment with$includeFullBioset totrue. Otherwise, we render a simple UI for showing the full bio. - Note the use of the function
UserFragment.makeRefetchVariablesto 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.makeRefetchVariablesis also autogenerated by RescriptRelay whenever there's a@refetchabledirective 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
$includeFullBiochanged tofalse, I'd domakeRefetchVariables(~includeFullBio=Some(false)).Some(false)here tells Relay that "I want to change$includeFullBioin the refetch tofalse. Use the last value for all other variables". - If I want to set
$bioMaxLengthto something, I'd domakeRefetchVariables(~bioMaxLength=Some(200)). This tells Relay "setbioMaxLength, leave everything else as is". - If I have
bioMaxLengthset 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
@refetchablewill give you auseRefetchablehook, which in turn gives you arefetchfunction that you can use to refetch the fragment. - When refetching, you provide only the parts of
variablesthat 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.