In this guide we are going to give a brief overview of how Relay works and how to use it, using as reference an example todo list app. For more thorough documentation, check out our Guides and API sections.
Table of Contents:
- Relay Environment
- Rendering GraphQL Queries
- Using Query Variables
- Using Fragments
- Composing Fragments
- Rendering Fragments
- Mutating Data
- Next Steps
Before we can start rendering pixels on the screen, we need to configure Relay via a Relay Environment. The environment bundles together the configuration, cache storage, and network-handling that Relay needs in order to operate.
For the purposes of our example, we are simply going to configure our environment to communicate with our existing GraphQL server:
A Relay Environment requires at least a Store and a Network Layer. The above code uses the default implementation for
Store, and creates a Network Layer using a simple
fetchQuery function to fetch a GraphQL query from our server.
Usually we'd want a single environment in our app, so you could export this environment as a singleton instance from a module to make it accessible across your app.
Now that we've configured our Relay Environment, we can start fetching queries and rendering data on the screen. The entry point to render data from a GraphQL query is the
QueryRenderer component provided by
To start, let's assume we just want to render the user id on the screen. From our schema, we know that we can get the current
User via the
viewer field, so let's write a sample query to fetch the current user id:
Now, let's see what it would take to create a component that fetches and renders the above query:
Our app is rendering a
QueryRenderer in the above code, like any other React Component, but let's see what's going on in the props that we are passing to it:
- We're passing the
environmentwe defined earlier.
- We're using the
graphqlfunction to define our GraphQL query.
graphqlis a template tag that is never executed at runtime, but rather used by the Relay Compiler to generate the runtime artifacts that Relay requires to operate. We don't need to worry about this right now; for more details check out our GraphQL in Relay docs.
- We're passing an empty set of
variables. We'll look into how to use variables in the next section.
- We're passing a
renderfunction; as you can tell from the code, Relay gives us some information about whether an error occurred, or if we're still fetching the query. If everything succeeds, the data we requested will be available inside
props, with the same shape as the one specified in the query.
In order to run this app, we need to first compile our query using the Relay Compiler. Assuming the setup from Installation and Setup, we can just run
For more details on
QueryRenderer, check out the docs.
Let's assume for a moment that in our app we want to be able to view data for different users, so we're going to somehow need to query users by id. From our schema, we know we can query nodes given an id, so let's write a parameterized query to get a user by id:
Now, let's see how we would fetch the above query using a
The above code is doing something very similar to our previous example. However, we are now passing a
$userID variable to the GraphQL query via the
variables prop. This has a couple of important implications:
- Given that
userIDis also a prop that our component takes, it could receive a new
userIDfrom its parent component at any moment. When this happens, new
variableswill be passed down to our
QueryRenderer, which will automatically cause it to re-fetch the query with the new value for
$userIDvariable will now be available anywhere inside that query. This will become important to keep in mind when using fragments.
Now that we've updated the query, don't forget to run
Now that we know how to define and fetch queries, let's actually start building a todo list.
First, let's start at the bottom. Suppose that we want to render a component that simply displays a given todo item's text and completed state:
From our schema, we know that we can query this data on the
Todo type. However, we don't want to have to send a separate query for each todo item; that would defeat the purpose of using GraphQL over a traditional REST API. We could manually query for these fields directly in our
QueryRenderer query, but that would hurt re-usability: what if we want to query the same set of fields as part of a different query? Additionally, we wouldn't know which component needs the data we're querying, which is a problem Relay directly tries to address.
Instead, we can define a reusable Fragment, which allows us to define a set of fields on a type and reuse them within our queries wherever we need to:
Our component can then use this fragment to declare its data dependency on the
Todo GraphQL type:
The above code highlights one of Relay's most important principles which is colocation of components with their data dependencies. This is beneficial for a few reasons:
- It becomes obvious at a glance what data is required to render a given component, without having to search which query in our app is fetching the required data.
- As a corollary, the component is de-coupled from the query that renders it. We can change the data dependencies for the component without having to update the queries that render them or worrying about breaking other components.
Check out our Thinking in Relay guide for more details behind Relay's principles.
Before proceeding, don't forget to run the Relay Compiler with
Given that Fragment Containers are just React components, we can compose them as such. We can even re-use fragment containers within other fragment containers. As an example, let's see how we would define a
TodoList component that just renders a list of todo items, and whether all have been completed or not:
As with the first fragment container we defined,
TodoList declares its data dependencies via a fragment. However, this component additionally re-uses the fragment previously defined by the
Todo component, and passes the appropriate data when rendering the child
Todo components (a.k.a. fragment containers).
One final thing to note when composing fragment containers is that the parent will not have access to the data defined by the child container. Relay only allows components to access data they specifically ask for in GraphQL fragments — nothing more. This is called Data Masking, and it's intentional to prevent components from depending on data they didn't declare as a dependency.
Now that we have some components (a.k.a fragment containers) that declare their data dependencies, we need to hook them up to a
QueryRenderer so that the data is actually fetched and rendered. Remember,
fragment containers do not directly fetch data. Instead, containers declare a specification of the data needed to render, and Relay guarantees that this data is available before rendering.
QueryRenderer rendering these fragment containers could look like the following:
Now that we know how to query for and render data, let's move on to changing our data. We know that to change any data in our server, we need to use GraphQL Mutations.
From our schema, we know that we have some mutations available to us, so let's start by writing a mutation to change the
complete status of a given todo item (i.e. mark or unmark it as done):
This mutation allows us to query back some data as a result of the mutation, so we're going to query for the updated
complete status on the todo item.
In order to execute this mutation in Relay, we're going to write a new mutation using Relay's
Whenever we call
ChangeTodoStatusMutation.commit(...), Relay will send the mutation to the server and, in our case, upon receiving a response it will automatically update the local data store with the latest data from the server. This also means that upon receiving the response, Relay will ensure that any components (i.e. containers) that depend on the updated data are re-rendered.
In order to actually use this mutation in our component, we could update our
Todo component in the following way:
In our example above, the
complete status in our component won't be updated and re-rendered until we get a response back from the server, which won't make for a great user experience.
In order to make the experience better, we can configure our mutation to do an optimistic update. An optimistic update means immediately updating our local data with what we expect it to be if we get a successful response from the server, i.e. updating the data immediately assuming that the mutation request will succeed. If the request doesn't succeed, we can roll-back our update.
In Relay, there's a couple of options we can pass to
commitMutation to enable optimistic updates. Let's see what that would look like in our
In the simplest case above, we just need to pass an
optimisticResponse option, which should refer to an object having the same shape as the mutation response payload. When we pass this option, Relay will know to immediately update our local data with the optimistic response, and then update it with the actual server response or roll it back if an error occurs.
Please note that the actual query and response payload may not have the exact same shape as the selection in your code, because sometimes Relay will add extra fields for you during the compilation step, and you need to add these fields to your optimistic response. For example:
Relay will add an
idfield if it exists on the type for caching purpose.
Relay will add a
__typenamefield if the type is an union or an interface.
You can inspect the network request or response to see the exact shape.
By default, Relay will know to update the fields on the records referenced by the mutation payload, (i.e. the
todo in our example). However, this is only the simplest case. In some cases updating the local data isn't as simple as just updating the fields in a record.
For instance, we might be updating a collection of items, or we might be deleting a record entirely. For these more advanced scenarios Relay allows us to pass a set of options for us to control how we update the local data from a server response, including a set of
configs and an
updater function for full control over the update.
For more details and advanced use cases on mutations and updates, check out our Mutations docs.
This guide just scratches the surface of Relay's API. For more detailed docs and guides, check out our API Reference and Guides sections.