relay Documentation

Relay is a JavaScript framework for building data-driven React applications.

18,963 stars Rust Rust 100 files · ~163,915 tokens
RAW Doc

Technical Documentation: facebook/relay

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (README.md)

[Relay](https://relay.dev) · [](https://github.com/facebook/relay/blob/main/LICENSE) [](https://www.npmjs.com/package/react-relay)

Relay is a JavaScript framework for building data-driven React applications.

  • Declarative: Never again communicate with your data store using an imperative API. Simply declare your data requirements using GraphQL and let Relay figure out how and when to fetch your data.
  • Colocation: Queries live next to the views that rely on them, so you can easily reason about your app. Relay aggregates queries into efficient network requests to fetch only what you need.
  • Mutations: Relay lets you mutate data on the client and server using GraphQL mutations, and offers automatic data consistency, optimistic updates, and error handling.

See how to use Relay in your own project.

Example

The relay-examples repository contains an implementation of TodoMVC. To try it out:

text
git clone https://github.com/relayjs/relay-examples.git
cd relay-examples/todo
yarn
yarn grats
yarn relay
yarn dev

Then, just point your browser at http://localhost:5173.

Contribute

We actively welcome pull requests, learn how to contribute.

Users

We have a community-maintained list of people and projects using Relay in production.

License

Relay is MIT licensed.

Thanks

We'd like to thank the contributors that helped make Relay in open source possible.

The open source project relay-hooks allowed the community to experiment with Relay and React Hooks, and was a source of valuable feedback for us. The idea for the useSubscription hook originated in an issue on that repo. Thank you @morrys for driving this project and for playing such an important role in our open source community.

Thank you for helping make this possible!


Principles And Architecture Architecture Overview (`website/docs/principles-and-architecture/architecture-overview.mdx`)


id: architecture-overview
title: Architecture Overview
slug: /principles-and-architecture/architecture-overview/
keywords:

  • architecture

import DocsRating from '@site/src/core/DocsRating';

This document, together with Runtime Architecture and Compiler Architecture, describes the high-level architecture of Relay. The intended audience includes developers interested in contributing to Relay, developers hoping to utilize the building blocks of Relay to create higher-level APIs, and anyone interested in understanding more about Relay internals. For developers wanting to learn more about using Relay to build products, the Tutorial is the best resource.

Core Modules

Relay is composed of three core parts:

  • Relay Compiler: A GraphQL to GraphQL optimizing compiler, providing general utilities for transforming and optimizing queries as well as generating build artifacts. A novel feature of the compiler is that it facilitates experimentation with new GraphQL features - in the form of custom directives - by making it easy to translate code using these directives into standard, spec-compliant GraphQL.
  • Relay Runtime: A full-featured, high-performance GraphQL runtime that can be used to build higher-level client APIs. The runtime features a normalized object cache, optimized "write" and "read" operations, a generic abstraction for incrementally fetching field data (such as for pagination), garbage collection for removing unreferenced cache entries, optimistic mutations with arbitrary logic, support for building subscriptions and live queries, and more.
  • React/Relay: A high-level product API that integrates the Relay Runtime with React. This is the primary public interface to Relay for most product developers, featuring APIs to fetch the data for a query or define data dependencies for reusable components (e.g. useFragment).

Note that these modules are loosely coupled. For example, the compiler emits representations of queries in a well-defined format that the runtime consumes, such that the compiler implementation can be swapped out if desired. React/Relay relies only on the well-documented public interface of the runtime, such that the actual implementation can be swapped out. We hope that this loose coupling will allow the community to explore new use-cases such as the development of specialized product APIs using the Relay runtime or integrations of the runtime with view libraries other than React.


Tutorial Intro (`website/docs/tutorial/intro.mdx`)

Tutorial Intro

This tutorial will get you started with the most important and frequently-used features of Relay. To do that, we’ll build a simple app that displays a newsfeed. We will cover:

This tutorial assumes a fair familiarity with React. If you’re still new to React, we suggest going through the React tutorial and working with React until you’re comfortable with creating components, passing props, and using the basic hooks such as useState. The tutorial is based on the Web, but Relay also works great with React Native.

This tutorial is built with TypeScript, so very basic knowledge of TypeScript is helpful as well — you don’t need to know anything beyond declaring and importing types and annotating functions. Relay can also be used with the Flow type system or without a type system.

:::info
IMPORTANT: The tutorial is meant to be gone through in order, as the exercises build on each other. You’ll be making incremental changes to an example app, so later section won’t make sense if you haven’t done the earlier sections.
:::

Setup

To get started, run the following commands:

text
git clone https://github.com/relayjs/relay-examples.git
cd relay-examples/newsfeed
npm install
npm run dev

This downloads a template project to get started from and starts the server. (If they don’t work, you may need to install git or install npm.)

When you run npm run dev, several processes are started:

  • A Webpack-based HTTP server that serves up the front-end code.
  • A basic GraphQL server that the front-end will query to retrieve information.
  • The Relay compiler, which processes the GraphQL in your app and generates additional files that Relay uses at runtime, as well as TypeScript types representing the inputs and results of your queries. It will automatically regenerate when you save changes in your files.

In the terminal output, these three processes’ log output are marked with tags: [webpack] in yellow, [server] in green, and [relay] in blue. Keep a look out for errors marked with [relay] as these are helpful if your GraphQL has any mistakes.

If you encounter errors in the [relay] process indicating: [relay] thread 'main' panicked at 'Cannot run relay in watch mode if `watchman` is not available (or explicitly disabled).', this means watchman is not installed or available on your system. To resolve this, you may need to install watchman separately. After installing watchman, try running npm run dev again.

Now that these processes are running, you should be able to open http://localhost:3000 in your browser.

We start from a webpage that shows a single Newsfeed story rendered with React, but the data for that story is just placeholder data hard-coded into the React components. In the rest of this tutorial, we’ll make the app functional by having it fetch data from the server, paginate over multiple stories, and update the data by commenting and liking.

Folder Structure

The files that make up the example app are laid out in this way:

  • src/components — the front-end app components that we’ll be modifying and working with. Some of the important components are:
    • App.tsx — the top-level component
    • Newsfeed.tsx — a component that will run a query to fetch newsfeed stories and display a scrolling list of stories
    • Story.tsx — a component that shows a single newsfeed story.
  • server — a very basic GraphQL server that serves up example data
    • server/schema.graphql — the GraphQL schema: it specifies what information can be queried from the server via GraphQL.

Finally, you may want to install the Relay VSCode extension for autocomplete, errors, and other help when using VSCode.

Head over to the next section to start learning about GraphQL and Relay.


Editor Support (`website/docs/editor-support.mdx`)


id: editor-support
title: Editor Support
slug: /editor-support/
keywords:

  • LSP
  • Language Server Protocol
  • VS Code
  • VSCode

import useBaseUrl from '@docusaurus/useBaseUrl';

TL;DR: We have a VS Code Extension


The Relay compiler has a rich understanding of the GraphQL embedded in your code. We want to use that understanding to improve the developer experience of writing apps with Relay. So, starting in v14.0.0, the new Rust Relay compiler can provide language features directly in your code editor. This means:

Relay compiler errors surface as red squiggles directly in your editor

Autocomplete throughout your GraphQL tagged template literals

Hover to see type information and documentation about Relay-specific features

`@deprecated` fields are rendered using ~~strikethrough~~

Click-to-definition for fragments, fields and types

Quick fix suggestions for common errors

Language Server

The editor support is implemented using the Language Server Protocol which means it can be used by a variety of editors, but in tandem with this release, Terence Bezman from Coinbase has contributed an official VS Code extension.

Find it here!

Why Have a Relay-Specific Editor Extension?

The GraphQL foundation has an official language server and VS Code extension which provides editor support for GraphQL generically. This can provide a good baseline experience, but for Relay users, getting this information directly from the Relay compiler offers a number of benefits:

  • Relay compiler errors can surface directly in the editor as “problems”, often with suggested quick fixes
  • Hover information is aware of Relay-specific features and directives and can link out to relevant documentation

Home (`website/docs/home.mdx`)


id: home
title: Home
slug: /
keywords:

  • relay
  • graphql
  • data
  • introduction
  • home

Relay Docs

import DocsRating from '@site/src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

Relay is a powerful GraphQL client for React. It embodies years of learning to give you outstanding performance by default while keeping your code scalable and maintainable.

Relay brings the composability of React components to data fetching. Each component declares its own data needs, and Relay combines them into efficient pre-loadable queries. Every aspect of its design is to make the natural way of writing components also the most performant.

Features

  • Declarative: Just declare what data each component needs and Relay will handle generating optimal queries for each surface.
  • Composable: Components act like building bricks that can click into place anywhere in your app without needing to manually update queries.
  • Pre-fetchable: Relay's generated queries allow you to start fetching data for your surface before your code even downloads or runs.
  • Built-in UI patterns: Relay implements loading states, pagination, refetching, optimistic updates, rollbacks, and other common UI behaviors that are tricky to get right.
  • Consistent state: Relay maintains a normalized data store, so components that observe the same data stay in sync even if they reach it by different queries.
  • Type safe: Relay generates TypeScript types for each GraphQL snippet so that errors are caught statically, not at runtime.
  • Streaming/deferred data: Declaratively defer parts of your query and Relay will progressively re-render your UI as the data streams in.
  • Developer experience: Relay's editor support provides autocompletion and go-to-definition for your GraphQL schema.

Community Learning Resources (`website/docs/community/learning-resources.mdx`)


id: learning-resources title: Community Learning Resources slug: /community-learning-resources/

import useBaseUrl from '@docusaurus/useBaseUrl';
import DocsRating from '@site/src/core/DocsRating';
import {FbInternalOnly, OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

Guides and articles:

Relay example projects

These projects serve as an example of how to use Relay in real world applications. Some of them are even with educational videos.

Learn basics

Here, you will find articles written by the Relay community. They touch on basic topics that are necessary for daily work.

About Relay Store

Network Layer

Relay Configuration

Miscellaneous


Debugging Declarative Mutation Directives (`website/docs/debugging/declarative-mutation-directives.mdx`)


id: declarative-mutation-directives
title: Debugging Declarative Mutation Directives
slug: /debugging/declarative-mutation-directives/
keywords:

  • debugging
  • troubleshooting
  • declarative mutation directive
  • deleteRecord
  • handlerProvider
  • appendEdge
  • prependEdge
  • appendNode
  • prependNode

// @fb-only

If you see an error similar to:

text
RelayFBHandlerProvider: No handler defined for `deleteRecord`. [Caught in: An uncaught error was thrown inside `RelayObservable`.]

or

text
RelayModernEnvironment: Expected a handler to be provided for handle `deleteRecord`.

This probably means that you are using a Relay environment to which a handlerProvider is passed. However, the handler provider does not know how to accept the handles "deleteRecord", "appendEdge" or "prependEdge". If this is the case, you should return MutationHandlers.DeleteRecordHandler, MutationHandlers.AppendEdgeHandler, or MutationHandlers.PrependEdgeHandler respectively (these can be imported from relay-runtime).

// @fb-only


Debugging Disallowed Id Types Error (`website/docs/debugging/disallowed-id-types-error.mdx`)


id: disallowed-id-types-error
title: Disallowed Types for id Fields
slug: /debugging/disallowed-id-types-error
keywords:

  • debugging
  • troubleshooting
  • disallowed
  • id
  • Object Identification

import DocsRating from '@site/src/core/DocsRating';

If you see an error from the compiler that reads something like:

text
Disallowed type `String` of field `id` on parent type `Foo` cannot be used by Relay to identify entities

This means that your GraphQL schema defines an object with a field named id that doesn't have a valid type. Valid types for this field are ID or ID! unless configured otherwise. While there may be a valid reason in your application to have that field defined that way, the Relay compiler and runtime will mishandle that field and cause refresh or data updating issues.

Disallowing `id` fields without type `ID`

Recall that Relay uses Object Identification to know which GraphQL objects to refetch. In the usual case, those GraphQL objects implement the Node interface and thus come with an id field with type ID. However, there are types in your GraphQL model that may not require unique identification. For that reason, Relay (by default) does not restrict object definitions, allowing id fields with non-ID types (e.g. String) to be defined.

This poses a bit of difficulty to both Relay's compiler and runtime. In short, the runtime and compiler only properly handle id fields as defined by the Node interface. Any selections made with non-Node id fields will likely exhibit undesirable and unintended Relay behavior on your components (e.g. components not re-rendering on re-fetched data).

The significance of the `ID` type

Global Object Identification in GraphQL) is what underlies Relay's Object Identification. The id field supplied by the Node interface is specified to be a unique identifier that can be used for storage or caching.

Fix: Define your `id` fields as `ID`

To ensure Relay correctly manages objects fetched to your application, here are two recommended courses of action:

  • Ensure all fields named id are typed with ID
  • Rename any fields named id (with a type that isn't ID) to a different name (e.g. special_purpose_id)

If your application truly requires that id field's definition to remain as-is and you are aware of the problems that may arise, you can add your GraphQL type and the type of its id field to the allowlist in nonNodeIdFields of the Relay Compiler's Configuration. Note that this will only bypass the error for that combination of object type and id field type.


Debugging Inconsistent Typename Error (`website/docs/debugging/inconsistent-typename-error.mdx`)


id: inconsistent-typename-error
title: Inconsistent Typename Error
slug: /debugging/inconsistent-typename-error/
keywords:

  • debugging
  • troubleshooting
  • inconsistent typename
  • RelayResponseNormalizer
  • globally unique id

import DocsRating from '@site/src/core/DocsRating';
import {FbInternalOnly, OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

Inconsistent `__typename` error

The GraphQL server likely violated the globally unique ID requirement by returning the same ID for different objects.

If you're seeing an error like:

text
`RelayResponseNormalizer: Invalid record '543'. Expected __typename to be consistent, but the record was assigned conflicting types Foo and Bar. The GraphQL server likely violated the globally unique ID requirement by returning the same ID for different objects.`

the server implementation of one of the types is not spec compliant. We require the id field to be globally unique. This is a problem because Relay stores objects in a normalized key-value store and one of the object just overwrote the other. This means your app is broken in some subtle or less subtle way.

Common cause

The most common reason for this error is that 2 objects backed by an ID are using the plain ID as the id field, such as a User and MessagingParticipant.

Less common reasons could be using array indices or auto-increment IDs from some database that might not be unique to this type.

Fix: Make your type spec compliant

The best way to fix this is to make your type spec compliant. For the case of 2 different types backed by the same ID, a common solution is to prefix the ID of the less widely used type with a unique string and then base64 encode the result. This can be accomplished fairly easily by implementing a NodeTokenResolver using the helper trait NodeTokenResolverWithPrefix. When the NodeTokenResolver is registered it allows you to load your type using node(id: $yourID) GraphQL call and your type can return an encoded ID.

Example

See D17996531 for an example on how to fix this. It created a FusionPlatformComponentsCategoryNodeResolver added the trait TGraphQLNodeMixin to the conflicting class so that it generates the base64 encoded ID.


Debugging Relay Devtools (`website/docs/debugging/relay-devtools.mdx`)


id: relay-devtools
title: Relay DevTools
slug: /debugging/relay-devtools/
keywords:

  • debugging
  • troubleshooting
  • extension
  • devtools
  • browser

import DocsRating from '@site/src/core/DocsRating';
import {FbInternalOnly, OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

Installation

Internal version (preferred)

The internal version of devtools has the latest updates and the process of installation will be much faster.

  1. Before downloading the new Devtools, make sure you've deleted all older versions of the extension.
  2. Join Relay Support group and you will automatically be added to the cpe_relay_devtools_extension gatekeeper.
  3. Wait 20-30 minutes, and it should be downloaded on your Chrome browser
  4. Or run sudo soloctl -i on your machine to get the extension immediately

Internal Version for Edgium users

  1. On C:\Users\\AppData\Local\Google\Chrome\User Data\<Work Profile>\Extensions search for files manifest.json with Relay Developer Tools on it
  2. Get the path to this folder e.g ...\Extensions\\<version>\
  3. On edge://extensions/ click load upacked (you might need to Allow extensions for other stores).

External version (use at your own risk)

The external version of Dev Tools does not always contain the latest updates, it may stop working, potentially being restricted to be used or removed by corp device admin (via MDM).
We recommend using the internal installation, but if you prefer, you may download the extension from the Chrome store.

Follow this link and install it from the chrome store.


How to Navigate the Relay DevTools Extension

You should have a new tab called Relay in your Chrome DevTools. In this tab, you will be able to select between 2 panels: the network panel and the store panel.

Network Panel

The network panel allows users to view individual requests in an active environment. Users can scroll through these requests, search for the requests and view the details of each request. The details of each request includes the status, the variables, and the responses for the request.

Store Panel

The store panel allows users to view individual records from the store data in an active environment. Users can scroll through these records, search for the records, and view the details of each request. Users can also copy the store data in JSON format to the clipboard. The details of each record includes the ID, the typename, and all of the data in the record. If one of the fields in the data is a reference to another record, users can click on the reference, which will take them to that record.


Multiple Environments

As you switch through the store and network panels, you'll notice that there is also a dropdown menu on the left side of the developer tools. This dropdown allows users to switch between environments. The requests in the network tab and the store data are grouped by environment, so users can easily shuffle between active environments.

Give Feedback

Look in the top-right corner of the extension panel!


Debugging Why Null (`website/docs/debugging/why-null.mdx`)


id: why-null
title: "Why Is My Field Null?"
slug: /debugging/why-null/
keywords:

  • "null"
  • missing
  • optional
  • nullthrows

import {FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
import DocsRating from '@site/src/core/DocsRating';

There are a number of reasons that a field read by Relay can be null and some of them are obscure or unintuitive. When debugging an unexpectedly null value, it can be helpful to understand both the common cases and edge cases that can cause a field to be read as null. This document enumerates the cases that can lead to null or missing values with tips for determining which case you are in.

Server Returned Null

The simplest reason a field might be null is that the server explicitly returned null. This can happen in two cases:

  1. The server’s field resolver returned an explicit null
  2. The field resolver throws. In this case GraphQL will return null for that field. This is true even if the server resolver’s return type is non-nullable. The one exception is fields annotated as non-null. In that case server should never return null. If an exception is encountered the entire parent object will be nulled out.

:::note
At Meta, non-nullable fields are implemented using KillsParentOnException.
:::

🕵️‍♀️ How to tell: Inspect the server’s response using Relay Dev tools, or in your browser’s dev tools’s network tab, to see if the field is null.

Graph Relationship Change

If a different query/mutation/subscription observes a relationship change in the graph, you may end up trying to read fields off of an object which your query never fetched.

Imagine you have a query that reads your best friend’s name:

graphql
query MyQuery {
  me {
    best_friend {
      # id: 1
      name
    }
  }
}

After you get your query response, who your best friend is changes on the server. Then a different query/mutation/subscription fetches a different set of fields off of best_friend.

graphql
query OtherQuery {
  me {
    best_friend {
      # new id: 2
      # Note: name is not fetched here
      age
    }
  }
}

Because the Relay store is normalized, we will update the me record to indicate that best_friend linked field now points to the user with ID 2, and the only information we know about that user is their age.

This will trigger a rerender of MyQuery. However, when we try to read the name field off of the user with ID 2, we won’t find it, since the only thing we know about the user with ID 2 is their age. Note that a relationship “change” in this case, could also mean a relationship that is new. For example, if you start with no best friend but a subsequent response returns some best friend, but does not fetch all fields your component needs.

Note: In theory, Relay could refetch your query when this state is encountered, but some queries are not safe to re-issue arbitrarily, and more generally, UI state changing in a way that’s not tied to a direct user action can lead to confusion. For this reason, we have chosen not to perform refetches in this scenario.

🕵️‍♀️ How to tell: You can place a breakpoint/console.log at the finale return statement of readWithIdentifier in FragmentResource (code pointer. This is the point in Relay at which we know that we are missing data, but there is not query in flight to get it.

Inconsistent Server Response

This is a rare edge case, but if the server does not correctly implement the field stability semantics of the id field, it’s possible that a field could be present in one part of the response, but explicitly null in another.

graphql
{
  me {
    id: 1
    name: "Alice"
  }
  me_elsewhere_in_the_graph {
    id: 1 # Note this is the same as the `me` field above...
    name: null
  }
}

In this case, Relay first learns that user 1’s name is Alice, but later in the query finds that user 1’s name has now null. Because Relay stores data in a normalized store, user 1 can only have one value for name and Relay will end in a state where user 1’s name is null.

🕵️‍♀️ How to tell: Relay is smart enough to detect when this has happened, and will log an error to the console in dev that looks like: “RelayResponseNormalizer: Invalid record. The record contains two instances of the same id: 1 with conflicting field, name and its values: Alice and null.". Additionally, you can manually inspect the query response.

Note that if the unstable field is a linked field (edge to another object), this type of bug can cause a Graph Relationship Change (described above) to occur within a single response. For example, if a user with the same id appears in two places in the response, but their best_friend is different in those two locations.

🕵️‍♀️ How to tell: Relay is also smart enough to detect this case, and will show a similar console warning in dev.

:::note
Because these errors exist in the codebase and can cause noisy console output, these warnings are disabled in dev mode.
:::

Client-side Deletion or Incomplete Update

Imperative store updates, or optimistic updates could have deleted the record or field. If an imperative store update, or optimistic update, writes a new record to the store, it may not supply a value for a field which you expected to be able to read. This is a fundamental problem, since an updater cannot statically know all the data that might be accessed off of a new object.

🕵️‍♀️ How to tell: Due to React and Relay’s batching, it’s not always possible to associate a component update with the store update that triggered it. Here, your best bet is to set a breakpoint in your component for when your value is null, and then use the Relay Dev Tools to look at the last few updates.

This can happen due to a newly created object which did not supply a specific field or, as mentioned above, an update which causes a new or changed relationship in the graph. In this case, use the “How do tell” tip from that section.


Error Reference Unknown Field (`website/docs/error-reference/unknown-field.mdx`)


id: unknown-field
title: "Why Is My Field Not Found?"
slug: /error-reference/unknown-field/
keywords:

  • no such field on type
  • missing
  • field
  • type
  • compilation
  • error

import {FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
import DocsRating from '@site/src/core/DocsRating';

[ERROR] Error in the project `some_project`: ✖︎ The type `Some_Type` has no field `some_unknown_field`.

Field name typo

In case of a missing field in a type, the Relay compiler tries to find and suggest a field replacement. For example: Error in the project `some_project`: ✖︎ The type `UserInfo` has no field `mail`. Did you mean `email`?

The field exists in another schema

Relay Compiler uses schemas in order to resolve types and their fields. The type's schema is determined by the file path and the mapping from file path to schema, which is configured in the "schema" or "schemaDir" properties of your Relay compiler config. If you expect this field to exist, make sure you're using the right schema.

:::note
At meta there are various project config files that are listed here.
:::


Getting Started Babel Plugin (`website/docs/getting-started/babel-plugin.mdx`)


id: babel-plugin
title: Relay Babel Plugin
slug: /getting-started/babel-plugin/
keywords:

  • babel

Babel Plugin

Relay requires a Babel plugin to convert GraphQL to compiler-generated runtime artifacts. Depending upon what framework/bundler you are using, there may be a framework-specific plugin you can use:

If not, you can install the Babel plugin manually:

sh
yarn add --dev babel-plugin-relay graphql

Add "relay" to the list of plugins in your .babelrc file:

javascript
{
  "plugins": ["relay"]
}

Please note that the "relay" plugin should run before other plugins or presets to ensure the graphql template literals are correctly transformed. See Babel's documentation on this topic.


Getting Started Compiler Config (`website/docs/getting-started/compiler-config.mdx`)


id: compiler-config
title: Compiler Configuration
slug: /getting-started/compiler-config/
keywords:

  • compiler
  • configuration
  • config
    hide_table_of_contents: false

import CompilerConfig from '@site/src/compiler-config/CompilerConfig';
import schema from '@compilerConfigJsonSchema';

Compiler Config Options

For information about where the Relay compiler looks for its config file, or a minimal config, see the Relay Compiler page.

If you need more advanced options of the Relay Compiler Config, the exhaustive full schema can be found below. The shape of the Relay Compiler Config is given as ConfigFile. Note that while the shapes are documented in pseudo TypeScript, the compiler is parsing them in Rust so some subtle differences may exist.

:::tip
Install the Relay VSCode extension to get autocomplete, hover tips, and type checking for the options in your Relay config.
:::


Getting Started Compiler (`website/docs/getting-started/compiler.mdx`)


id: compiler
title: Relay Compiler
slug: /guides/compiler/
keywords:

  • compiler

Relay depends upon ahead-of-time compilation of GraphQL queries and fragments to generate artifacts that are used at runtime.

The Relay compiler is a command line tool that reads GraphQL fragments, queries, and mutations in your JavaScript code and generates TypeScript/Flow types and additional JavaScript code that gets included in your code via Relay's Babel Plugin.

This guide explains configuring and using the Relay Compiler.

Configuration

The Relay compiler will look for a Relay config in the following locations. It's up to you to decide which location works best for your project.

  • relay.config.json in your project root
  • relay.config.(js/mjs/ts) in your project root
  • A "relay" key in your package.json

The Relay compiler config tells Relay things like where it can find your GraphQL schema and what language your code is written in. A minimal Relay compiler config looks like this:

json title="relay.config.json"
{
  "src": "./src",
  "schema": "./schema.graphql",
  "language": "typescript"
}

:::tip
Install the Relay VSCode extension to get autocomplete, hover tips, and type checking for the options in your relay config.
:::

The compiler config is very powerful, and includes many specialized configuration options. For a full enumeration of the available options see the Compiler Configuration page.

Running the compiler

It is generally recommended that you add a scripts entry to your package.json to make it easy to run the Relay compiler for your project.

json title="package.json"
{
  "scripts": {
    // change-line
    "relay": "relay-compiler"
  }
}

With this added you can run the Relay compiler like so:

sh
npm run relay

Watch mode

If you have watchman installed you can pass --watch to the Relay compiler to have it continue running and automatically update generated files as you edit your product code:

sh
relay-compiler --watch

Validate mode

Pass --validate to check whether any generated artifacts are out of date without writing anything to disk. The compiler exits with a non-zero code if any artifacts need to be updated. This is useful in CI to assert that generated files are committed and current:

sh
relay-compiler --validate

Disabling watchman

By default, Relay uses watchman, if installed, for fast file discovery. To fall back to directory traversal instead, pass --no-watchman:

sh
relay-compiler --no-watchman

Codemods

The Relay compiler supports some built in codemods. Learn more in the Codemods Guide.

Document comparison (Experimental)

The Relay compiler can compare two GraphQL documents to determine if one is a subset of another. Learn more in the Document Comparison Guide.

Help

To see the full command-line reference run:

sh
relay-compiler --help
relay-compiler <subcommand> --help

Getting Started Lint Rules (`website/docs/getting-started/lint-rules.mdx`)


id: lint-rules
title: Relay ESLint Plugin
slug: /getting-started/lint-rules/
keywords:

  • eslint
  • lint

One of the unique features enabled by Relay is the ability to statically detect unused GraphQL fields. This can categorically prevent the "append only query" problem that is a common dysfunction in many GraphQL clients.

This validation, and other helpful checks, are enabled by Relay's ESLint plugin eslint-plugin-relay. The Relay ESLint plugin is a key part of the Relay developer experience.

Installation

Assuming you have ESLint already installed, you can add the Relay ESLint plugin to your project by running:

sh
npm install --save-dev eslint-plugin-relay

Then update your ESLint configuration to include the plugin:

js tile="eslint.config.js"
import relay from 'eslint-plugin-relay';

export default [
  // ... other ESlint Config
  {
    plugins: { relay },
    rules: relay.configs['ts-recommended'].rules,
  },
];

Rule Descriptions

The following validation rules are included in the Relay ESLint plugin:

`relay/unused-fields`

Ensures that every GraphQL field referenced is used within the module that includes it. This helps enable Relay's optimal data fetching.

`relay/no-future-added-value`

Ensures code does not try to explicitly handle the "%future added value" enum variant which Relay inserts as a placeholder to ensure you handle the possibility that new enum variants may be added by the server after your application has been deployed.

`relay/graphql-syntax`

Ensures each graphql tagged template literal contains syntactically valid GraphQL. This is also validated by the Relay Compiler, but the ESLint plugin can often provide faster feedback.

`relay/graphql-naming`

Ensures GraphQL fragments and queries follow Relay's naming conventions. This is also validated by the Relay Compiler, but the ESLint plugin can often provide faster feedback.

`relay/function-required-argument`

Ensures that readInlineData is always passed an explicit argument even though that argument is allowed to be undefined at runtime.

`relay/hook-required-argument`

Ensures that Relay hooks are always passed an explicit argument even though that argument is allowed to be undefined at runtime.

`relay/must-colocate-fragment-spreads`

Ensures that when a fragment spread is added within a module, that module directly imports the module which defines that fragment. This prevents the anti-pattern when one component fetches a fragment that is not used by a direct child component.
Note: This rule leans heavily on Meta's globally unique module names. It likely won't work well in other environments.

Suppressing rules within graphql tags

The following rules support suppression within graphql tags:

  • relay/unused-fields
  • relay/must-colocate-fragment-spreads

Supported rules can be suppressed by adding # eslint-disable-next-line relay/name-of-rule to the preceding line:

js
graphql`
  fragment foo on Page {
    # eslint-disable-next-line relay/must-colocate-fragment-spreads
    ...unused1
  }
`;

Note that only the eslint-disable-next-line form of suppression works. eslint-disable-line doesn't currently work until graphql-js provides support for parsing Comment nodes in their AST.

Contributing

If you wish to contribute to the Relay ESLint plugin, you can find the code on GitHub at relay/eslint-plugin-relay.


Getting Started Production (`website/docs/getting-started/production.mdx`)


id: production
title: Production Setup
slug: /getting-started/production/
keywords:

  • production

Getting the most out of Relay in production requires a few extra steps. This page covers a list of best practices for taking Relay to production.

Persisted Queries (Trusted Documents)

One of GraphQL's key innovations is that it enables clients to define arbitrary queries. While this unlocks a lot of flexibility, it also opens the doors to abuse. Scrapers can use GraphQL to scrape data from your site, and malicious users can use GraphQL to send large requests to your server that cause a denial of service. To prevent this, we recommend using Persisted Queries also know as Trusted Documents.

With Persisted Queries, the set of queries that the client can send is locked in at build time. This means that scrapers and malicious attackers are limited to sending only the queries used by the client, just like REST. It also has the added benefit that the client code only needs to include a single id for each query rather than the whole query text.

Relay Lint Rules

A key principle of Relay is data colocation where each component defines its own data dependencies. It's critical to How Relay Enables Optimal Data Fetching. However, this is often unintuitive for developers who are used to fetching data in a single query. Relay's ESLint rules can help enforce this pattern by ensuring no component fetches fields which it does not itself use.

We recommend using the eslint-plugin-relay, especially the relay/unused-fields rule.

Learn how to install them: Relay ESLint Plugin.

Running the Relay Compiler in CI

We recommend committing Relay's generated artifacts to source control along with your application code. This ensures generated types are present without needing an additional build, and allows for inspection of generated artifacts in code review. To ensure the generated artifacts are always in sync with the source code, we recommend running the Relay compiler in CI and ensuring it does not change any generated files. A example bash script which checks for changes can be found here.


Getting Started Quick Start (`website/docs/getting-started/quick-start.mdx`)


id: quick-start
title: Quick Start
slug: /getting-started/quick-start/
keywords:

  • quick

This quick start guide will start with a new React app using Vite and show you how to add Relay to it.

:::tip
If you'd prefer an automated approach, create-relay-app by Tobias Tengler will walk you through adding Relay to an existing React app via a series of prompts: npm create @tobiastengler/relay-app
:::

We will be building a simple app which shows Star Wars movies fetched from the example Star Wars GraphQL API hosted by graphql.org.

Scaffold a React App

We’ll start with a Vite React app using TypeScript.

bash
npm create vite -- --template react-ts

You’ll be prompted for a project name. Type: relay-example

Install Dependencies

bash
cd relay-example

# Runtime dependencies
npm install relay-runtime react-relay
# Dev dependencies
npm install --save-dev babel-plugin-relay graphql relay-compiler @rolldown/plugin-babel

:::tip
If you're using an AI coding assistant, install the Relay best practices skill to make your agent a Relay expert:

bash
npx skills install facebook/relay

:::

Configure Vite to use Relay

Relay uses a Babel plugin to insert code generated by the Relay compiler into your bundle. We can enable the Relay Babel plugin we installed earlier by configuring the Babel Vite plugin.

tsx title="vite.config.ts"
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// change-line
import babel from "@rolldown/plugin-babel";

// https://vite.dev/config/
export default defineConfig({
  // change-line
  plugins: [react(), babel({ plugins: ["relay"] })],
});

See Babel Plugin for information about how to configure the Babel plugin for other build systems.

Configure the Relay Compiler

Next we will download the GraphQL schema for the Star Wars GraphQL endpoint.

bash
curl -O https://raw.githubusercontent.com/graphql/swapi-graphql/refs/heads/master/schema.graphql

And define our relay.config.json config file which tells the Relay Compiler which schema file we want it to use and other details about our project.

json title="relay.config.json"
{
  "src": "./src",
  "schema": "./schema.graphql",
  "language": "typescript"
}

See Relay Compiler for more information about configuring and running the Relay compiler.

Configure your Relay Environment

To allow components within our application to fetch GraphQL we configure a Relay Environment to fetch from our test endpoint and add it to React context.

tsx title="src/main.tsx"
import type { FetchFunction } from "relay-runtime";

import { StrictMode, Suspense } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";
import { RelayEnvironmentProvider } from "react-relay";
import { Environment, Network } from "relay-runtime";

const HTTP_ENDPOINT = "https://graphql.org/graphql/";

const fetchGraphQL: FetchFunction = async (request, variables) => {
  const resp = await fetch(HTTP_ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ query: request.text, variables }),
  });
  if (!resp.ok) {
    throw new Error("Response failed.");
  }
  return await resp.json();
};

const environment = new Environment({
  network: Network.create(fetchGraphQL),
});

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <RelayEnvironmentProvider environment={environment}>
      <Suspense fallback="Loading...">
        <App />
      </Suspense>
    </RelayEnvironmentProvider>
  </StrictMode>
);

See Relay Environment for an overview of the Relay Environment and how to configure it.

:::tip
<RelayEnvironmentProvider> exposes your Environment via React context, so it must wrap your entire application.
:::

Define your first Relay component

Finally we can start defining the data we want to fetch and build our UI. Our app will fetch a list of films and render each one using a <Film> component.

tsx title="src/app.tsx"
import type { AppQuery } from "./__generated__/AppQuery.graphql";

import { graphql, useLazyLoadQuery } from "react-relay";
import Film from "./Film.tsx";

export default function App() {
  const data = useLazyLoadQuery<AppQuery>(
    graphql`
      query AppQuery {
        allFilms {
          films {
            id
            ...Film_item
          }
        }
      }
    `,
    {}
  );

  const films = data?.allFilms?.films?.filter((film) => film != null);

  return (
    <div>
      <h1>Star Wars Films</h1>
      {films?.map((film) => (
        <Film key={film.id} film={film} />
      ))}
    </div>
  );
}

Define your first fragment

One of Relay's core principles is that each component should define its own data dependencies. So, we define our <Film> component using a GraphQL Fragment.

typescript title="src/film.tsx"
import { graphql, useFragment } from "react-relay";
import type { Film_item$key } from "./__generated__/Film_item.graphql";

export default function FilmListItem(props: { film: Film_item$key; }) {
  const film = useFragment<Film_item$key>(
    graphql`
      fragment Film_item on Film {
        title
        director
      }
    `,
    props.film
  );

  return (
    <li>
      <b>{film.title}</b>: directed by <i>{film.director}</i>
    </li>
  );
}

Compile and run your app

All that’s left is to run the Relay compiler and start your app!

The Relay compiler generates TypeScript types and combines your queries and fragments into optimized representations. You have to run the Relay compiler each time you modify your GraphQL queries or fragments.

:::tip
If you have Watchman installed you can run npx relay-compiler --watch to have the compiler run in watch mode, but you'll need to run echo "{}" > .watchmanconfig to create a Watchman root.
:::

bash
npx relay-compiler
npm run dev

You should now be able to open your app in a browser: http://localhost:5173/

May the force be with you!


Glossary Glossary (`website/docs/glossary/glossary.mdx`)


id: glossary
title: Glossary
slug: /glossary/
keywords:

  • glossary

import DocsRating from '@site/src/core/DocsRating';
import {FbInternalOnly, OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

3D

Data-Driven Dependencies. Facebook's way of including the code to render a particular component if and only if it will actually be rendered. Canonical use cases are

  • Fields that are typically null, and which are only rendered when not null.
  • Unions. For example, the core news feed item has many different variants, each of which is a separate React component. Which one we render depends on the data (i.e. is "data-driven"). On a given feed, it is likely that most variants will not be rendered, and need not be downloaded.
  • Component can have different rendering strategies, depending on the data.

See the @match directive, @module directive and the 3D guide.

See the @match directive and the @module directive.

Abstract Type

GraphQL unions and interfaces are abstract types. See interface.

Abstract Type Refinement

See type refinement. If type refinement is a way of conditionally including fields if a type implements a particular concrete type (such as ... on User { name }), abstract type refinement refers to conditionally including fields if a type implements a particular abstract type (i.e. interface). So, ... on Actor { field }.

@arguments

A directive that modifies a fragment spread and is used to pass arguments (defined with @argumentDefinitions) to that fragment.

graphql
...Story_story @arguments(storyId: "1234")

@argumentDefinitions

A directive that modifies a fragment definition and defines the names of the local arguments that the fragment can take, as well as their type.

graphql
fragment Store_story on Story
  @argumentDefinitions(storyId: {type: "ID!"}) {
  # etc
}

If a variable is used in a fragment but not included in an @argumentDefinitions directive, Relay will require that the fragment is only spread in queries which declare these variables, or in fragments which ultimately are spread in such a query.

Compare with variables and see the relevant section in the guided tour.

Artifact

Files that are generated by the Relay compiler, typically ending in .graphql.js.

See a guide to Relay artifacts.

AST

Abstract Syntax Tree. In Relay, there are two types of ASTs, normalization and reader ASTs.

The default export of a .graphql.js file is an AST.

The Relay compiler parses and transforms GraphQL literals, and generates Relay ASTs (see artifact). Doing this work at compile time allows the Relay runtime to be faster.

Availability

The concept of availability refers to whether there is enough non-stale, non-invalidated data in the store to fulfill a particular request immediately, or whether a request to server needs to be made in order to fulfill that request.

Babel Transform

A build-time transformation of the Javascript codebase, which turns calls to

javascript
graphql`...`

into require(NAME_OF_GENERATED_ARTIFACT) calls. See Babel Plugin.

Client Schema Extension

The GraphQL spec allows you to define new types, new fields on types, new directives, etc. locally.

Relay supports adding types and fields in client schema extension files. Developers use this feature to add fields that contain purely local state that is associated with items on the graph. For example, an is_selected field on a User.

CacheConfig

A value used to control how a query's response may be cached. Ultimately passed to environment.execute.

Check

One of the core functions of the store. Given an operation, determines whether the store has all of the data necessary to render that operation. Calls DataChecker.check, which synchronously starts with the root node associated with the operation and walks the data in the store.

In practice, exposed as a method on environment.

In conjunction with the fetch policy, used by loadQuery (and other methods) to determine whether it is necessary to make a network request call to fulfill a query.

Commit

After receiving a network response, the payload is committed, or written to the store.

Commit is also the verb used to describe initiating a mutation and writing its data to the store.

Compiler

The piece of code which scans your Javascript files for graphql tagged nodes and generates the appropriate files (.graphql.js files, $Parameters.js files, etc.)

The generated output from the compiler is committed and checked into the repository.

Concrete Request

An Abstract Syntax Tree representing a query, subscription or mutation.

The default export of a .graphql.js file corresponding to a query, subscription or mutation.

In addition, calls to graphql...`` are turned into concrete requests at build time via the Relay Babel transform.

See the important safety notes at Preloadable Concrete Request.

Config

A file or javascript object which controls, among other things, which files are scanned by the Relay compiler for your project.

@connection

A directive which declares that a field implements the connection spec.

Connection

A field implementing the connection spec. See here for more details on the spec, and the section of the guided tour on rendering list data and pagination.

A field implementing the connection spec. See the section of the guided tour on rendering list data and pagination.

See also usePaginationFragment.

Container

A term for a higher order component that provided a child component with the data from queries and fragments. Associated with Relay Modern.

You should use the Relay hooks API when possible.

Data Checker

A class exposing a single method, check, which synchronously starts with the root node associated with the operation and walks the data in the store. It determines whether the data in the store suffices to fulfill a given operation.

Called by store.check.

DataID

The globally-unique identifier of a record. Can be generated on the client with missing field handlers. Usually corresponds to an Ent's ID (if available), but guaranteed to equal the value of the __id field.

updater and optimisticUpdater functions are passed instances of RelaySourceSelectorProxy. Calling .get(id) with the DataID on a RelaySourceSelectorProxy will look up that item in the store, and return a proxy of it.

Data Masking

Refers to the idea that a component should not be able to access any data it does declare in its fragment or query, even inadvertently. This prevents accidental coupling of data between components, and means that every component can be refactored in isolation. It negates the risk that removing a field in a child component will accidentally break a different component, allowing components to move fast, with stable infrastructure.

Also refers to the practice of hiding the data of child components from their parents, in keeping with the idea.

In Relay, a query declared like query FooQuery { viewer { ...subcomponent_``viewer_name } } will not be able to access the data declared by subcomponent_viewer_name without access to the ReaderFragment representing the subcomponent_viewer_name fragment.

See the Thinking in Relay guide.

@defer

A directive which can be added to a fragment spread or inline fragment to avoid blocking on that fragment's data. For more detail refer to GraphQL's documentation on the @defer directive.

See Incremental Data Delivery.

Definition

In the compiler, a definition refers to the text within a GraphQL literal where an operation or fragment was defined.

Descriptor

Can refer to an OperationDescriptor or RequestDescriptor. Descriptors are types used internally to the Relay codebase, and generally, refer to an object containing the minimum amount of information needed to uniquely identify an operation or request, such as (for a RequestIdentifier), a node, identifier and variables.

DevTools

An awesome Chrome extension for debugging Relay network requests, the Relay store and Relay events. Helpful for answering questions like "Why am I not seeing the data I expect to see?" "Why did this component suspend?" etc.

See the documentation.

Document

In the compiler, a Document refers to a GraphQL literal that contains one or more operation or fragment definitions. Relay requires that GraphQL literals in JavaScript files contain a single definition.

Directive

A special instruction, starting with @ and contained in a graphql literal or graphql file, which provides special instructions to the Relay compiler or to the server. Examples include @defer, @stream and @match.

Disposable

Any object which contains a .dispose method which takes no parameters and provides no return value. Many objects in Relay (such query references and entrypoint references) and the return value of many methods (such as calls to .subscribe or .retain) are disposables.

Entrypoint

A lightweight object containing information on the components which need to be loaded (as in the form of calls to JSResource) and which queries need to be loaded (in the form of preloadable concrete requests) before a particular route, popover or other piece of conditionally loaded UI can be rendered.

All queries which are required for the initial rendering of a piece of UI should be included in that UI's entrypoint.

Entrypoints can contain queries and other entrypoints.

See also preloadable concrete request and JSResource.

Environment

An object bringing together many other Relay objects, most importantly a store and a network. Also, includes a publish queue, operation loader, scheduler and missing fields handlers.

Set using a RelayEnvironmentProvider and passed down through React context.

All non-internal Relay hooks require being called within a Relay environment context.

Execute

Executing a query, mutation or subscription (collectively, an operation) roughly means "create a lazy observable that, when subscribed to, will make a network request fulfilling the operation and write the returned data to the store."

A variety of execute methods are exposed on the Relay environment.

Fetch Policy

A string that determines in what circumstances to make a network request in which circumstances to fulfill the query using data in the store, if available. Either network-only, store-and-network, store-or-network or store-only. (Some methods do not accept all fetch policies.)

Field

Basically, anything you can select using a query, mutation, subscription or fragment. For example, viewer, comment_create(input: $CommentCreateData) and name are all fields.

The GraphQL schema comprises many fields.

Fragment

Fragment is an overloaded term, and has at least two distinct meanings in Relay.

Fragments as a GraphQL concept

The fundamental reusable unit of GraphQL. Unlike queries, subscriptions and mutations, fragments cannot be queried on their own and must be embedded within a request.

Fragments can be spread into queries, mutations, subscriptions and other fragments.

Fragments can be standalone (as in fragment Component_user on User { name }) or inline, as in the ... on User { name } in query MyQuery { node(id: $id) { ... on User { name } } }.

Fragments are always defined on a particular type (User in the example), which defines what fields can be selected within it.

Fragments within Relay

Within Relay, a fragment refers to the fields that are read out for a given fragment/operation. The term is also used colloquially to refer to reader ASTs. So, e.g. the following query and fragment might have identical reader ASTs:

graphql
query Foo {
  actor { name }
}
text
fragment Bar on Query {
  actor { name }
}

Fragment Identifier

A string, providing enough information to provide the data for a particular fragment. For example:

1234{"scale":2}/Story_story/{"scale":2}/"4567"

This identifies by its persist ID (1234), followed by the variables it accepts, followed by the Story_story fragment (which does not have a persist id) and the variables it uses, followed by the Data ID (likely, the id field) of whatever Story happened to be referenced.

Fragment Reference

A parameter passed to useFragment. Obtained by accessing the value onto which a fragment was spread in another query, fragment, subscription or mutation. For example,

javascript
const queryData = usePreloadedQuery(
  graphql`query ComponentQuery { viewer { account_user { ...Component_name } } }`,
  {},
);

// queryData.viewer is the FragmentReference
// Though this would usually happen in another file, you can
// extract the value of Component_name as follows:
const fragmentData = useFragment(
  graphql`fragment Component_name on User { name }`,
  queryData?.viewer?.account_user,
);

Just like a query reference and a graphql tagged literal describing a query (i.e. a concrete request) can be used to access the data from a query, a fragment reference and a graphql tagged literal describing a fragment (i.e. a reader fragment) can be used to access the data referenced from a fragment.

Fragment Resource

An internal class supporting lazily loaded queries. Exposes two important methods:

  • read, which is meant to be called during a component's render phase. It will attempt to fulfill a query from the store (by calling environment.lookup) and suspend if the data is not available. It furthermore writes the results from the attempted read (whether a promise, error or result) to an internal cache, and updates that cached value when the promise resolves or rejects.
  • subscribe, which is called during the commit phase, and establishes subscriptions to the Relay store.

If the component which calls .read successfully loads a query, but suspends on a subsequent hook before committing, the data from that query can be garbage collected before the component ultimately renders. Thus, components which rely on FragmentResource are at risk of rendering null data.

Compare to query resource.

Fragment Spread

A fragment spread is how one fragment is contained in a query, subscription, mutation or other fragment. In the following example, ...Component_name is a fragment spread:

graphql
query ComponentQuery {
  viewer {
    account_user {
      ...Component_name
    }
  }
}

In order for a fragment to be spread in a particular location, the types must match. For example, if Component_name was defined as follows: fragment Component_name on User { name }, this spread would be valid, as viewer.account_user has type User.

Garbage Collection

Relay can periodically garbage collect data from queries which are no longer being retained.

See more information in the guided tour.

GraphQLTaggedNode

This is the type of the call to

js
graphql`...`

It is the union of ReaderFragment, ReaderInlineDataFragment, ConcreteRequest, and ConcreteUpdatableQuery.

Note that Flow can be configured to understand that the type of a GraphQL literal is the type of the default export of the generated .graphql.js file.

Note that Flow is configured to understand that the type of a GraphQL literal is the type of the default export of the generated .graphql.js file.

ID

Relay treats ids specially. In particular, it does the following two things:

  • The compiler automatically adds a selection of the id field on every type where the id field has type ID or ID!.
  • When normalizing data, if an object has an id property, that field is used as its ID in the store.

There are types in the schema where the id field does not have type ID or ID! (e.g. has the type string or number). If a user selects this field themselves, this field is used as an id. This is unexpected and incorrect behavior.

@include

A directive that is added to fields, inline fragments and fragment spreads, and allows for conditional inclusion. It is the opposite of the @skip directive.

In the compiler, the @include/@skip directives are treated specially, and produce Condition nodes.

@inline

A directive that applies to fragments which enables developers to pass masked data to functions that are executed outside of the React render phase.

Normally, data is read out using useFragment. However, this function can only be called during the render phase. If store data is needed outside of the render phase, a developer has several options:

  • read that data during the render phase, and pass it to the function/have the function close over that data. (See also [#relay])
  • pass a reference to an @inline fragment, which can then be accessed (outside of the render phase) using the readInlineData function.

This directive causes them to be read out when the parent fragment is read out, and unmasked by the call to readInlineData.

Interface (GraphQL)

An Interface is an abstract type that includes a certain set of fields that a type must include to implement the interface.

You can spread a fragment on an interface onto a concrete type (for example query MyQuery { viewer { account_user { ...on Actor { can_viewer_message } } }) or a fragment on a concrete type onto an interface (for example query MyQuery { node(id: 4) { ... on User { name } } }). You are no longer allowed to spread a fragment on an interface onto an interface.

See also abstract type refinement.

Invalidation

In certain cases, it is easy to determine the outcome of a mutation. For example, if you "like" a Feedback, the like count will increment and viewer_did_like will be set to true. However, in other cases, such as when you are blocking another user, the full impact on the data in your store is hard to determine.

For situations like these, Relay allows you to invalidate a record (or the whole store), which will cause the data to be re-fetched the next time it is rendered.

See the section in the guide.

JSResource

A lightweight API for specifying that a React component should be loaded on demand, instead of being bundled with the first require (as would be the case if you imported or required it directly.)

This API is safe to use in entrypoint files.

See the npm module.

Lazy Loading

A query or entry point is lazy loaded if the request for the data occurs at render time.

Lazy loaded queries and entry points have performance downsides, are vulnerable to being over- and under-fetched, and can result in components being rendered with null data. They should be avoided.

Linked Record

A linked record is a record that is directly accessible from another record. For example, in the query query MyQuery { viewer { account_user { active_instant_game { id } } } }, active_instant_game (which has the type Application is a linked record of account_user.

A linked record cannot be queried by itself, but must be queried by selecting subfields on it.

Compare to value.

Literal

A GraphQL literal is a call to

javascript
graphql`...`

in your code. These are pre-processed, and replaced at build time with a GraphQLTaggedNode containing an AST representation of the contents of the literal.

@live

A docblock tag that can be added to mark a Relay resolver as live. To learn more, refer to the live fields section of the Relay resolver documentation.

@live_query

A directive used on GraphQL queries that enables data updates to be delivered over time without any custom server-side code. This directive provides a more efficient and maintainable alternative to polling (running the same query over and over again).

Live queries are a feature of GraphQL within Meta and supported by the Real-Time GraphQL team. To learn more about GraphQL live queries, refer to the GraphQL Live Queries wiki.

You can learn more about how to use @live_query with Relay on Web with server-polling here and client-polling here.

Lookup

One of the main methods exposed by the Relay store. Using a reader selector, traverses the data in the store and returns a snapshot, which contains the data being read, as well as information about whether data is missing and other pieces of information. Also exposed via the Relay environment.

Calls Reader.read.

@match

A directive that, when used in combination with @module, allows users to download specific JS components alongside the rest of the GraphQL payload if the field decorated with @match has a certain type. See 3D.

MatchContainer

A component that renders the component returned in conjunction with a field decorated with the @match directive. See 3D.

Missing Field Handler

A function that provides a DataID for a field (for singular and plural linked fields) and default values (for scalar fields).

For example, you may have already fetched an item with id: 4, and are executing a query which selects node(id: 4). Without a missing field handler, Relay would not know that the item with id: 4 will be returned by node(id: 4), and would thus attempt to fetch this data over the network. Providing a missing field handler can inform Relay that the results of this selection are present at id: 4, thus allowing Relay to avoid a network request.

getRelayFBMissingFieldHandlers.js provides this and other missing field handlers.

@module

A directive that, when used in combination with @match, allows users to specify which JS components to download if the field decorated with @match has a certain type. See 3D.

Module

A "Module" refers to a React component or a piece of JavaScript code.

In the context of Relay, modules can by dynamically loaded with the @module directive. This directive allows developers to specify which JavaScript components should be downloaded (from a CDN for example) based on the type of a field decorated with the @match directive. This approach is part of Relay's data-driven dependencies strategy, where components are only loaded if they are actually needed for rendering.

Mutation

A mutation is a combination of two things: a mutation on the backend, followed by query against updated data.

See the guide on mutations, and this article on defining mutations in your hack code.

See the guide on mutations.

Mutation Root Query

The root object of a mutation query. In an updater or optimisticUpdater, calling store.getRootField('field_name') will return the object from the mutation root query named field_name.

The fields exposed on this object are not the same as those available for queries, and differ across mutations.

Network

Relay environments contain a network object, which exposes a single execute function. All network requests initiated by Relay will go through this piece of code.

This provides a convenient place to handle cross-cutting concerns, like authentication and authorization.

Node

A Node is an object that implements the Node interface in GraphQL, which requires an id field of type ID. This is part of the Global Object Identification specification that provides a standardized way to fetch and identify objects in a GraphQL schema.

In Relay, the Node interface is fundamental to how data normalization and caching work. When Relay encounters an object with an id field, it uses that ID as the key for storing the object in its normalized cache. This enables efficient data deduplication, updates, and garbage collection.

Nodes can be refetched using the node field on the Query root type by passing their global ID: query { node(id: "abc123") { ... } }. This allows Relay to fetch data for any object implementing the Node interface without needing to know the specific field path to that object.

Normalization

Normalization is the process of turning nested data (such as the server response) and turning it into flat data (which is how Relay stores it in the store.)

See the response normalizer.

Normalization AST

An AST that is associated with an operation that (in combination with variables) can be used to:

  • write a network payload to the store,
  • write an optimistic response to the store,
  • determine whether a query can be fulfilled from data in the store, and
  • determine which records in the store are reachable (used in garbage collection).

Unlike the reader AST, the normalization AST includes information on the contents of nested fragments.

The generated artifact associated with an operation (e.g. FooQuery.graphql.js) contains both a normalization AST and a reader AST.

Normalization Selector

A selector defines the starting point for a traversal into the graph for the purposes of targeting a subgraph, combining a GraphQL fragment, variables, and the Data ID for the root object from which traversal should progress.

Notify

A method exposed by the store which will notify each subscriber whose data has been modified. Causes components which are rendering data that has been modified to re-render with new data.

Observable

The fundamental abstraction in Relay for representing data that may currently be present, but may also only be available in the future.

Observables differ from promises in that if the data in an observable has already been loaded, you can access it synchronously as follows:

javascript
const completedObservable = Observable.from("Relay is awesome!");
let valueFromObservable;
observable.subscribe({
  next: (value) => {
    valueFromObservable = value;
    /* this will execute in the same tick */
  },
});
console.log(valueFromObservable); // logs out "Relay is awesome!"

This is advantageous, as it allows Relay hooks to not suspend if data is already present in the store.

In Relay, observables are a partial implementation of RxJS Observables.

Operation

In GraphQL, a query, subscription or mutation.

In Relay, every operation also has an associated fragment. So, an accurate mental model is that operations are fragments whose type condition is that they are on Query/Mutation/Subscription and for which Relay knows how to make a network request.

Operation Descriptor

Colloquially, an operation descriptor is an operation and variables.

The operation descriptor flowtype contains the three pieces of information that Relay needs to work with the data: a reader selector, a normalization selector and a request descriptor.

The variables are filtered to exclude unneeded variables and are populated to include default values for missing variables, thus ensuring that requests that differ in irrelevant ways are cached using the same request ID.

Operation Mock Resolver

A function taking an operation descriptor and returning a network response or error, used when testing.

Operation Tracker

The Relay Operation Tracker is a component within the Relay runtime that manages and tracks the lifecycle of operations (queries, subscriptions, and mutations), in the Relay store. It maintains mappings between operations and affected data owners, manages promises for asynchronous updates, and resolves dependencies by notifying subscribers when data changes, ensuring components render with the latest data. For more implementation details of the Operation Tracker, see the OSS code here.

Optimistic Update

An Optimistic Update in Relay is a technique used to immediately reflect the expected outcome of a mutation in the UI before the server response is received.

This approach enhances user experience by providing instant feedback for actions, such as liking a post, without waiting for the server to confirm the change.

Optimistic updates assume the mutation will succeed and temporarily update the store with the anticipated result. If the mutation fails, the optimistic update is rolled back to maintain data consistency. This is typically achieved by providing an optimisticResponse in the mutation configuration, which specifies the expected changes to the data.

See the documentation on optimistic updates for examples of how to use this feature.

Optimistic Updater

An Optimistic Updater in Relay is a function that allows developers to imperatively modify the store's data in anticipation of a mutation's success.

Unlike optimistic responses, which are declarative, optimistic updaters provide more control and flexibility, enabling updates to data not directly selected in the mutation or complex changes that cannot be handled by declarative mutation directives.

Optimistic updaters are executed when a mutation is triggered and are rolled back if the mutation fails. They are particularly useful for scenarios where multiple optimistic responses affect the same store value, and fine grained control is needed by the developer to ensure that the store remains consistent.

See the documentation on optimistic updaters for examples of how to use them.

Pagination

Querying a list of data (a connection) in parts is known as pagination.

See the graphql docs and our guided tour.

Payload

The value returned from the GraphQL server as part of the response to a request.

Plural Field

A field for which the value is an array of values or records.

@preloadable

A directive that modifies queries and which causes Relay to generate $Parameters.js files and preloadable concrete requests. Required if the query is going to be used as part of an entry point.

Preloadable Concrete Request

A small, lightweight object that provides enough information to initiate the query and fetch the full query AST (the ConcreteRequest.) This object will only be generated if the query is annotated with @preloadable, and is the default export of $parameters.js files. It is only generated for queries which are annotated with @preloadable.

Unlike concrete requests (the default export of .graphql.js files), preloadable concrete requests are extremely light weight.

Note that entrypoints accept either preloadable concrete requests or concrete requests in the .queries[queryName].parameters position. However, because a concrete request is not a lightweight object, you should only include preloadable concrete requests here.

Note also that preloadable queries have id fields, whereas other queries do not.

Preloadable Query Registry

A central registry which will execute callbacks when a particular Query AST (concrete request) is loaded.

Required because of current limitations on dynamically loading components in React Native.

Project

For Relay to process a file with a GraphQL literal, it must be included in a project. A project specifies the folders to which it applies and the schema against which to evaluate GraphQL literals, and includes other information needed by the Relay compiler.

Projects are defined in a single config file, found here and here.

Publish

One of the main methods exposed by the store. Accepts a record source, from which the records in the store are updated. Also updates the mapping of which records in the store have been updated as a result of publishing.

One or more calls to publish should be followed by a call to notify.

Publish Queue

A class used internally by the environment to keep track of, apply and revert pending (optimistic) updates; commit client updates; and commit server responses.

Exposes mutator methods like commitUpdate that only add or remove updates from the queue, as well as a run method that actually performs these updates and calls store.publish and store.notify.

Query

A GraphQL query is a request that can be sent to a GraphQL server in combination with a set of variables, in order to fetch some data. It consists of a selection of fields, and potentially includes other fragments.

Query Executor

A class that normalizes and publishes optimistic responses and network responses from a network observable to the store.

After each response is published to the store, store.notify is called, updating all components that need to re-render.

Used by environment in methods such as execute, executeWithSource and executeMutation, among others.

Query Reference

A Query Reference is an opaque token that represents a query that we have started fetching, but may not have been returned by the network yet. It's returned by functions like loadQuery and serves as an identifier for which query we want to render.

Query references are intentionally opaque to the user - they don't expose the query's internal state or data directly. Instead, they are passed to usePreloadedQuery to access the data from a preloaded query.

The query reference pattern enables efficient data loading by allowing queries to be initiated early (such as in response to user interactions like hovering or route changes) and then consumed later when the component actually renders.

Query references are disposable objects - they should be disposed of when no longer needed to prevent memory leaks and allow proper cleanup of network requests and subscriptions.

Query Resource

A class for helping with lazily loaded queries and exposing two important methods: prepare and retain.

  • prepare is called during a component's render method, and will either read an existing cached value for the query, or fetch the query and suspend. It also stores the results of the attempted read (whether the data, a promise for the data or an error) in a local cache.
  • retain is called after the component has successfully rendered.

If the component which calls .prepare successfully loads a query, but suspends on a subsequent hook before committing, the data from that query can be garbage collected before the component ultimately renders. Thus, components which rely on QueryResource are at risk of rendering null data.

Compare to fragment resource.

`@raw_response_type`

A directive added to queries which tells Relay to generate types that cover the optimisticResponse parameter to commitMutation.

See the guided tour on updating data for more.

Reader

A core component of the Relay runtime responsible for reading data from the store. The Reader traverses the normalized data in the store using a reader AST and constructs the denormalized data structure that matches the shape expected by components.

The Reader is called by the store's lookup method and is fundamental to how Relay provides data to React components. It handles data masking by only exposing the fields that were explicitly selected in fragments and queries, ensuring components can only access the data they declared dependencies for.

The Reader also manages missing data detection, determining when insufficient data is available in the store to fulfill a request, which helps Relay decide when network requests are necessary.

Reader AST

An AST that is used to read the data selected in a given fragment.

Both operations and fragments have reader ASTs.

A reader AST contains information about which fragments are spread at a given location, but unlike a normalization AST, does not include information about the fields selected within these fragments.

Reader Fragment

TODO

See GraphQLTaggedNode.

Reader Selector

An object containing enough information for the store to traverse its data and construct an object represented by a query or fragment. Intuitively, this "selects" a portion of the object graph.

See also lookup.

Record

A record refers to any item in the Relay store that is stored by ID. Values are not records; most everything else is.

Record Source

An abstract interface for storing records, keyed by DataID, used both for representing the store's cache for updates to it.

Record Source Selector Proxy

See record proxy.

Record Proxy

See the store documentation.

Ref Counting

The pattern of keeping track of how many other objects can access a particular object, and cleaning it up or disposing of it when that number reaches zero. This pattern is implemented throughout the Relay codebase.

Reference Marker

The Relay Reference Marker is a component within the Relay runtime responsible for traversing the data graph and marking references to data that should be retained in the Relay store. It ensures data consistency by preventing premature garbage collection of data required for operations or fragments. The marker handles various data selections, manages variables, and interacts with the operation loader to process module imports, ensuring that all necessary data dependencies are retained.
See the OSS code for implementation details.

@refetchable

A directive that modifies a fragment, and causes Relay to generate a query for that fragment.

This yields efficiency gains. The fragment can be loaded as part of a single, larger query initially (thus requiring only a single request to fetch all of the data), and yet refetched independently.

@relay

A directive that allows you to turn off data masking and is used on plural types.

See the documentation.

Relay Classic

An even older version of Relay.

Relay Hooks

The easiest-to-use, safest Relay API. It relies on suspense, and is safe to use in React concurrent mode.

You should not write new code using Relay Classic or Relay Modern.

Relay Modern

An older version of Relay. This version of Relay had an API that was heavily focused on Containers.

Relay Resolvers

Relay Resolvers is an experimental Relay feature which enables modeling derived state as client-only fields in Relay’s GraphQL graph.

See also the Relay Resolvers Introduction.

Release Buffer

As queries are released (no longer retained), their root nodes are stored in a release buffer of fixed size, and only evicted by newly released queries when there isn't enough space in the release buffer. When Relay runs garbage collection, queries that are present in the release buffer and not disposed.

The size of the release buffer is configured with the gcReleaseBufferSize parameter.

`@required`

A Relay directive that makes handling potentially null values more ergonomic.

See also the @required guide.

Request

A request refers to an API call made over the network to access or mutate some data, or both.

A query, when initiated, may or may not involve making a request, depending on whether the query can be fulfilled from the store or not.

Request Descriptor

An object associating a concrete request and variables, as well as a pre-computed request ID. The variables should be filtered to exclude unneeded variables and are populated to include default values for missing variables, thus ensuring that requests that differ in irrelevant ways are cached using the same request ID.

Resolver

An overloaded term, mostly referring to virtual fields, but also occasionally referring to other things.

When describing a field

A resolver field is a "virtual" field that is backed by a function from a fragment reference on the same type to some arbitrary value.

A live resolver is a "virtual" field that is backed by an external data source. e.g. one might use an external resolver to expose some state that is stored in local storage, or in an external Flux store.

Other meanings

It can also be a fragment spec resolver or a operation mock resolver.

Response

The data returned by a GraphQL server in response to a query, mutation, or subscription. A GraphQL response typically contains a data field with the requested information, and may also include an errors field if any errors occurred during execution.

In Relay, responses are processed by the Response Normalizer, which transforms the nested response data into a flat, normalized format suitable for storage in the Relay store. This normalization process enables efficient data deduplication, caching, and updates.

Responses can also include extensions and other metadata depending on the GraphQL server implementation. Relay handles both successful responses and error responses, providing appropriate error handling and data consistency guarantees.

Response Normalizer

A class, exposing a single method normalize. This will traverse the denormalized response from the API request, normalize it and write the normalized results into a given MutableRecordSource. It is called from the query executor.

Restore

TODO

Retain

TODO

Render Policy

The "Render Policy" in Relay determines how data is rendered in components, specifically whether to use "full" or "partial" rendering. This policy integrates with React's Suspense to manage when components should suspend based on data availability.

  • Full Rendering: This approach waits until all required data is available before rendering a component. It ensures that components do not render with incomplete data, which can prevent flickering or unexpected UI changes.
  • Partial Rendering: This approach allows components to render with the data that is currently available, even if some data is missing. It can improve perceived performance by displaying parts of the UI sooner, but may lead to components rendering with null or incomplete data.

The choice between full and partial rendering can be configured per environment or query, allowing developers to optimize for specific use cases.

For example, Comet defaults to partial rendering to accommodate certain product behaviors, while other environments may use full rendering to ensure data consistency.

Revert

TODO

Root Field

A root field is a top-level field on a GraphQL root type — Query, Mutation, or Subscription. These fields serve as entry points for GraphQL operations, allowing clients to fetch, modify, or subscribe to data.

Root Type

The GraphQL spec defines three special root types: Query, Mutation and Subscription. Queries must select fields off of the Query root type, etc.

Root

Outermost React Component for a given page or screen. Can be associated with an entrypoint.

Roots for entrypoints are referred to by the JSResource to the root React component module.

Scalar

A scalar in GraphQL is a primitive, indivisible leaf value that cannot have subfields. Scalars represent basic data types, such as numbers, strings, booleans, and identifiers, which are returned directly to the client without further selection or nesting.

GraphQL defines several built-in scalar types:

  • Int: A signed 32‐bit integer
  • Float: A signed double-precision floating-point value
  • String: A UTF‐8 character sequence
  • Boolean: true or false
  • ID: A unique identifier, often serialized as a string

In addition to these built-in types, GraphQL servers may define custom scalars for values like dates, times, or other domain-specific primitive types.

Relay represents GraphQL scalars as corresponding primitive JavaScript values (such as numbers, booleans, and strings). For custom scalars, Relay treats them as opaque values, providing support for their representation and use, but does not interpret their content.

For more details, see:

Scheduler

TODO

Schema

A collection of all of the GraphQL types that are known to Relay, for a given project.

Schema Sync

The GraphQL schema is derived from annotations on Hack classes in the www repository.

Periodically, those changes are synced to fbsource in a schema sync diff. If the updated schema would break Relay on fbsource, these schema sync diffs will not land.

If a field is removed from www, but is only used in fbsource, the application developer may not notice that the field cannot be removed. This is a common source of schema breakages.

For more info, look here and here.

Selection

A "selection of fields" refers to the fields you are requesting on an object that you are accessing, as part of a query, mutation, subscription or fragment.

Selector

See normalization selector.

@skip

A directive that is added to fields, inline fragments and fragment spreads, and allows for conditional inclusion. It is the opposite of the @include directive.

Snapshot

The results of running a reader selector against the data currently in the store. See lookup.

Stale

In Relay, "stale" describes cached data in the client store that is no longer considered fresh or up-to-date. By default, Relay treats cached data as valid indefinitely unless it is explicitly marked as stale through invalidation APIs (invalidateRecord, invalidateStore) or if it exceeds the configured query cache expiration time. When data is stale, Relay triggers a network refetch on the next query evaluation to ensure the UI reflects the most current information. Components can also subscribe to invalidation events to respond immediately when data becomes stale (useSubscribeToInvalidationState hook). This mechanism helps maintain data consistency and timely updates after changes such as mutations.

For more details, see the Relay Staleness of Data documentation.

Store

The Relay Store is a local cache in the Relay runtime that manages the lifecycle of records fetched from a GraphQL server, as well as client-side data. It normalizes and de-dupes data, supports subscriptions and optimistic updates, and handles garbage collection and data retention to ensure efficient data access and consistency.

In addition to server-fetched data, the Relay Store can manage client-side data, including data from resolvers and local state. Client-side resolvers compute derived data based on existing records, allowing components to access both server and client data seamlessly. The store also supports the integration of local state by adding client-specific fields to the GraphQL schema, enabling developers to query and manage local state using Relay.

@stream

A directive which can be added to a field of List type that enables the individual items in the list to be delivered incrementally. The client can render the initial
set of items while waiting for the server to deliver the rest of the items. For more detail refer to GraphQL's documentation on the @stream directive.

@stream_connection

A directive that is like the @connection directive for pagination, except modified to enable items in the pagination queue to be delivered incrementally. It has the additional parameter initial_count to specify how many items to deliver in the initial payload. To learn more about how to use this directive refer to the Streaming Pagination page.

Subscribe

A method exposed by the Relay store. Accepts a callback and a snapshot (see lookup). The Relay store will call this callback when notify is called, if the data referenced by that snapshot has been updated or invalidated.

Subscription

GraphQL Subscriptions are a mechanism which allow clients to subscribe to changes in a piece of data from the server, and get notified whenever that data changes.

A GraphQL Subscription looks very similar to a query, with the exception that it uses the subscription keyword:

graphql
subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) {
  feedback_like_subscribe(data: $input) {
    feedback {
      id
      like_count
    }
  }
}

See also the guide.

Transaction ID

A unique id for a given instance of a call to network.execute. This ID will be consistent for the entire duration of a network request. It can be consumed by custom log functions passed to RelayModernEnvironment.

Traversal

There are four tree traversals that are core to understanding the internal behavior of Relay.

  • Using the normalization AST:
    • When Relay normalizes the payload it receives from the GraphQL server in the Response Normalizer;
    • When Relay determines whether there is enough data for to fulfill an operation, in the Data Checker; and
    • When Relay determines what data is no longer accessible during garbage collection, in the Reference Marker.
  • Using the reader AST:
    • When Relay reads data for rendering, in the Reader.

Type

The GraphQL type of a field is a description of a field on a schema, in terms of what subfields it has, or what its representation is (String, number, etc.).

See also interface, abstract type and the GraphQL docs for more info.

Type Refinement

The inclusion of a fragment of particular type in a location only known to potentially implement that type. This allows us to select fields if and only if they are defined on that particular object, and return null otherwise.

For example, node(id: 4) { ... on User { name } }. In this case, we do not know ahead of time whether node(id: 4) is a User. If it is, this fragment will include the user name.

See also abstract type refinement.

Updater

A callback passed to commitMutation, which provides the application developer with imperative control over the data in the store.

See the documentation and also optimistic updater.

Value

A single value on a record, such as has_viewer_liked, or name.

Compare with linked record.

Variables

GraphQL variables are a construct that allows referencing dynamic values inside a GraphQL query. They must be provided when the query is initiated, and can be used throughout nested fragments.

See the variables section of the guided tour and compare with @argumentDefinitions.


Guides Alias Directive (`website/docs/guides/alias-directive.mdx`)


id: alias-directive
title: "@alias Directive"
slug: /guides/alias-directive/
keywords:

  • alias
  • directive
  • fragment

The @alias directive allows you to expose a spread fragment — either a named fragment spread or an inline fragment — as a named field within your selection. This allows Relay to provide additional type safety in the case where your fragment’s type may not match the parent selection.

:::info
This document describes why the @alias directive was introduced, and how it can be used to improve type safety in your Relay applications. To learn about its API, see the API Reference.
:::

Let's look at an example where @alias can be useful:

Abstract Types

Imagine you have a component that renders information about a Viewer:

ts
function MyViewer({viewerKey}) {
  const {name} = useFragment(graphql`
    fragment MyViewer on Viewer {
      name @required(action: THROW)
    }`, viewerKey);

  return `My name is ${name}. That's ${name.length} letters long!`;
}

To use that component in a component that has a fragment on Node (which Viewer implements), you could write something like this:

ts
function MyNode({nodeKey}) {
  const node = useFragment(graphql`
    fragment MyFragment on Node {
      ...MyViewer
    }`, nodeKey);

  return <MyViewer viewerKey={node} />
}

Can you spot the problem? We don’t actually know that the node we are passing to <MyViewer /> is actually a Viewer <MyViewer />. If <MyNode /> tries to render a Comment — which also implements Node — we will get a runtime error in <MyViewer /> because the field name is not present on Comment.

text
TypeError: Cannot read properties of undefined (reading 'length')

Not only do we not get a type letting us know that about this potential issue, but even at runtime, there is no way to check if node implements Viewer because Viewer is an abstract type!

Aliased Fragments

Aliased fragments can solve this problem. Here’s what <MyNode /> would look like using them:

ts
function MyNode({nodeKey}) {
  const node = useFragment(graphql`
    fragment MyFragment on Node {
      ...MyViewer @alias(as: "my_viewer")
    }`, nodeKey);

  // Relay returns the fragment key as its own nullable property
  if(node.my_viewer == null) {
    return null;
  }

  // Because `my_viewer` is typed as nullable, Flow/TypeScript will
  // show an error if you try to use the `my_viewer` without first
  // performing a null check.
  //                          VVVVVVVVVVVVVV
  return <MyViewer viewerKey={node.my_viewer} />
}

With this approach, you can see that Relay exposes the fragment key as its own nullable property, which allows us to check that node actually implements Viewer and even allows Flow to enforce that the component handles the possibility!

@skip and @include

A similar problem can occur when using @skip and @include directives on fragments. In order to safely use the spread fragment, you need to check if it was fetched. Historically this has required gaining access to the query variable that was used to determine if the fragment was skipped or included.

With @alias, you can now check if the fragment was fetched by simply assigning the fragment an alias, and checking if the alias is null:

ts
function MyUser({userKey}) {
  const user = useFragment(graphql`
    fragment MyFragment on User {
      ...ConditionalData @skip(if: $someVar) @alias
    }`, userKey);

  if(user.ConditionalData == null) {
    return "No data fetched";
  }
  return <ConditionalData userKey={user.ConditionalData} />
}

Enforced Safety

We've outlined two different ways that fragments can be unsafe in Relay today without @alias. To help prevent runtime issues resulting from these unsafe edge cases, Relay requires that all conditionally fetched fragments are aliased.

To disable this validation in your project, you can disable the enforce_fragment_alias_where_ambiguous compiler feature flag for your project. If you need to enable incremental adoption of this enforcement, Relay exposes a directive @dangerously_unaliased_fixme which will suppress enforcement errors. This will allow you to enable the enforcement for all new spreads without first needing to migrate all existing issues.

The Relay VSCode extension offers quick fixes to add either @alias or @dangerously_unaliased_fixme to unsafe fragments, and the
mark-dangerous-conditional-fragment-spreads codemod can be used to apply @dangerously_unaliased_fixme across your entire project.

Use with @required

@alias can be used with @required(action: NONE) to group together required fields. In the following example, we group name and email together as requiredFields. If either is null, that null will bubble up to, the user.requiredFields field, making it null. This allows us to perform a single check, without impacting the id field.

ts
function MyUser({userKey}) {
  const user = useFragment(graphql`
    fragment MyFragment on User {
      id
      ... @alias(as: "requiredFields") {
        name @required(action: NONE)
        email @required(action: NONE)
      }
    }`, userKey);

  if(user.requiredFields == null) {
    return `Missing required fields for user ${user.id}`;
  }
  return `Hello ${user.requiredFields.name} (${user.requiredFields.email}).!`;
}

:::note
Using @required on a fragment spread that has an @alias is not currently supported, but we may add support in the future.
:::

Under the Hood

For people familiar with Relay, or curious to learn, here is a brief description of how this feature is implemented:

Under the hood, @alias is implemented entirely within Relay (compiler and runtime). It does not require any server support. The Relay compiler interprets the @alias directive, and generates types indicating that the fragment key, or inline fragment data, will be attached to the new field, rather than directly on the parent object. In the Relay runtime artifact, it wraps the fragment node with a new node indicating the name of the alias and additional information about the type of the fragment.

The Relay compiler also inserts an additional field into the spread which allows it to determine if the fragment has matched:

graphql
fragment Foo on Node {
  ... on Viewer {
    isViewer: __typename # <-- Relay inserts this
    name
  }
}

Relay can now check for the existence of the isViewer field in the response to know if the fragment matched.

When Relay reads the content of your fragment out of the store using its runtime artifact, it uses this information to attach the fragment key to this new field, rather than attaching it directly to the parent object.

While @alias is a Relay-specific feature, it draws inspiration from fragment modularity as outlined in the GraphQL RFC Fragment Modularity.


Guides Catch Directive (`website/docs/guides/catch-directive.mdx`)


id: catch-directive
title: '@catch Directive'
slug: /guides/catch-directive/
keywords:

  • catch
  • directive
  • optional

import DocsRating from '@site/src/core/DocsRating';

The @catch directive can be added to fields, fragment/operation definitions or
aliased inline fragment spreads declare how exceptions and unexpected values
should be handled at runtime. Using @catch allows Relay to surface these error
states as part of your fragment/query/mutation data instead of a null value
(which has been the default behavior), or a runtime exception if
@throwOnFieldError is being used.

:::tip
Both @catch and @throwOnFieldError only handle field errors in the
query/fragment/mutation in which they are used. They do not handle errors
related to fields in any spread fragments.
:::

`to` Argument

The @catch directive accepts an optional to argument which has two options:

  • RESULT (default): The value is returned as { ok: true, value: T } | { ok: false, errors: [error] }. This allows you implement explicit field-granular error handling in your application.
  • NULL: If an error is encountered within the @catch, the value will be replaced with null.

Examples

If a @catch error is caught directly on the field that the error originated
from - the error is provided on that field. Here's an example:

graphql
query MyQuery {
  viewer {
    name @catch
    age
  }
}

If name contains an error - it would be provided in the response data on the
name field - like so:

js
{
  viewer: {
    name: {
      ok: false,
      errors: [{path: ['viewer', 'name']}]
    }
    age: 39
  }
}

However, if @catch exists on one of the ancestors of a field, that error will
bubble up to there, like so:

graphql
query MyQuery {
  viewer @catch {
    name
    age
  }
}
js
{
  viewer: {
    ok: false,
    errors: [{ path: ['viewer', 'name'] } ]
  }
}

Implications for nullability

Fields whose errors are explicitly handled by @catch, either by being
annotated with @catch or by being nested with a @catch ancestor will be
typed using their Semantic Nullability. In other
words, if a field has been marked as @semanticNonNull in the server schema to
indicate that it will only be null in the case of error, Relay will type that
field as non-nullable in its generated Flow/TypeScript types.

What can be caught with `@catch`?

Payload Field Errors

Payload field
errors
are
errors that occur as the result of a server-side exception while executing a
given field's resolver. In this case, the GraphQL specifies that the server must
provide a null value where a value should be, and a separate errors object.

When you @catch on a field, Relay takes those errors and provides them to you
in-line instead, making them easier to handle, and no longer invisible.

@required(action: THROW) within an @catch

If you have an @required(action: THROW) with an ancestor that contains a
@catch - instead of throwing an exception, the @required error would bubble
up and be provided in the same way normal errors would. Here's an example:

graphql
query MyQuery {
  viewer @catch {
    name @required(action: THROW)
    age
  }
}
js
{
  viewer: {
    ok: false,
    errors: [{ path: ["viewer", "name"] }]
  }
}

Missing Data in response

Here is an example of where missing data may occur in Relay

If a field is expected to have a value, and that field is undefined - the field
is considered to be "missing data". This is also an unexpected state - and when
it happens with an @catch as an ancestor, it will also be caught like so:

js
{
  viewer: {
    ok: false,
    errors: [{ path: ["viewer", "name"] }]
  }
}

How does `@catch` interact with `@throwOnFieldError`?

Using @throwOnFieldError enables fields to throw a JavaScript exception when a
field error occurs. By using @catch - you tell Relay that you don't want a
JavaScript exception in this case. Instead, you are requesting that the error be
provided in the data object, with the same behaviors and rules as are listed
above (including bubbling to a parent field).

It is important to note that you can still use @catch without
@throwOnFieldError. It will still provide you the error in the data object. But
other fields that are not under a @catch will still not throw - because
@throwOnFieldError would be missing.

Read more about @throwOnFieldError
here.

GraphQL Conf Talk

The Relay team gave a talk at GraphQL Conf 2024 about @catch and explicit error handling in Relay. You can watch it here:


Guides Client Schema Extensions (`website/docs/guides/client-schema-extensions.mdx`)


id: client-schema-extensions
title: Client Schema Extensions
slug: /guides/client-schema-extensions/
keywords:

  • client
  • schema
  • extension
  • commitLocalUpdate

import DocsRating from '@site/src/core/DocsRating';

:::note
See also the local data updates and client-only data sections of the guided tour.
:::

Relay can be used to read and write local data, and act as a single source of truth for all data in your client application.

The Relay Compiler fully supports client-side extensions of the schema, which allows you to define local fields and types.

Table of Contents:

Extending the server schema

To extend the server schema, create a new .graphql file inside your --src
directory. Let's call it ./src/clientSchema.graphql. This file needs to be
referenced in the "schemaExtensions" of your Relay config, either directly or
via its folder.

This schema describes what local data can be queried on the client.
It can even be used to extend an existing server schema.

For example, we can create a new type called Note:

graphql
type Note {
  id: ID!
  title: String
  body: String
}

And then extend the server schema type User, with a list of Note, called notes.

graphql
extend type User {
  notes: [Note]
}

Querying local state

Accessing local data is no different from querying your GraphQL server, although you are required to include at least one server field in the query.
The field can be from the server schema, or it can be schema agnostic, like an introspection field (e.g. __typename).

Here, we use useLazyLoadQuery to get the current User via the viewer field, along with their id, name and the local list of notes.

javascript
// Example.js
import * as React from 'react';
import { useLazyLoadQuery, graphql } from 'react-relay';

const Example = (props) => {
  const data = useLazyLoadQuery(graphql`
    query ExampleQuery {
      viewer {
        id
        name
        notes {
          id
          title
          body
        }
      }
    }
  `, {});
  // ...
}

Mutating local state

All local data lives in the Relay Store.

Updating local state can be done with any updater function.

The commitLocalUpdate function is especially ideal for this, because writes to local state are usually executed outside of a mutation.

To build upon the previous example, let's try creating, updating and deleting a Note from the list of notes on User.

Create

javascript
import {commitLocalUpdate} from 'react-relay';

let tempID = 0;

function createUserNote(environment) {
  commitLocalUpdate(environment, store => {
    const user = store.getRoot().getLinkedRecord('viewer');
    const userNoteRecords = user.getLinkedRecords('notes') || [];

    // Create a unique ID.
    const dataID = `client:Note:${tempID++}`;

    //Create a new note record.
    const newNoteRecord = store.create(dataID, 'Note');

    // Add the record to the user's list of notes.
    user.setLinkedRecords([...userNoteRecords, newNoteRecord], 'notes');
  });
}

Note that since this record will be rendered by the ExampleQuery via useLazyLoadQuery, the query data will automatically be retained and won't be garbage collected.

If no component is rendering the local data and you want to manually retain it, you can do so by calling environment.retain():

javascript
import {createOperationDescriptor, getRequest} from 'relay-runtime';

// Create a query that references that record
const localDataQuery = graphql`
  query LocalDataQuery {
    viewer {
      notes {
        __typename
      }
    }
  }
`;

// Create an operation descriptor for the query
const request = getRequest(localDataQuery);
const operation = createOperationDescriptor(request, {} /* variables */);


// Tell Relay to retain this operation so any data referenced by it isn't garbage collected
// In this case, all the notes linked to the `viewer` will be retained
const disposable = environment.retain(operation);


// Whenever you don't need that data anymore and it's okay for Relay to garbage collect it,
// you can dispose of the retain
disposable.dispose();

Update

javascript
import {commitLocalUpdate} from 'react-relay';

function updateUserNote(environment, dataID, body, title) {
  commitLocalUpdate(environment, store => {
    const note = store.get(dataID);

    note.setValue(body, 'body');
    note.setValue(title, 'title')
  });
}

Delete

javascript
import {commitLocalUpdate} from 'react-relay';

function deleteUserNote(environment, dataID) {
  commitLocalUpdate(environment, store => {
    const user = store.getRoot().getLinkedRecord('viewer');
    const userNoteRecords = user.getLinkedRecords('notes');

    // Remove the note from the list of user notes.
    const newUserNoteRecords = userNoteRecords.filter(x => x.getDataID() !== dataID);

    // Delete the note from the store.
    store.delete(dataID);

    // Set the new list of notes.
    user.setLinkedRecords(newUserNoteRecords, 'notes');
  });
}

Initial local state

All new client-side schema fields default to undefined value. Often however, you will want to set the initial state before querying local data.
You can use an updater function via commitLocalUpdate to prime local state.

javascript
import {commitLocalUpdate} from 'react-relay';

commitLocalUpdate(environment, store => {
  const user = store.getRoot().getLinkedRecord('viewer');

  // initialize user notes to an empty array.
  user.setLinkedRecords([], 'notes');
});

Guides Codemods (`website/docs/guides/codemods.mdx`)


id: codemods
title: Codemods
slug: /guides/codemods/
keywords:

  • codemod
  • codemods

import DocsRating from '@site/src/core/DocsRating'; import {FbInternalOnly,
OssOnly, fbContent} from 'docusaurus-plugin-internaldocs-fb/internal'; import
Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

The Relay compiler has the ability to make changes across the source files of
your projects with the use of codemods. You can see the list of codemods
available by running the Relay compiler's codemod command:

text
> relay codemod --help
Apply codemod (verification with auto-applied fixes)

Usage: relay codemod [OPTIONS] [CONFIG] <COMMAND>

Commands:
  mark-dangerous-conditional-fragment-spreads  Marks unaliased conditional fragment spreads as @dangerously_unaliased_fixme
  remove-unnecessary-required-directives       Removes @required directives from non-null fields within @throwOnFieldError fragments and operations.
  fix-all                                      Runs all Relay compiler transforms and fixes all fixable diagnostics
  help                                         Print this message or the help of the given subcommand(s)

Arguments:
  [CONFIG]  Compile using this config file. If not provided, searches for a config in package.json under the `relay` key or `relay.config.json` files among other up from the current working directory

Options:
  -p, --project <project>  Compile only this project. You can pass this argument multiple times. to compile multiple projects. If excluded, all projects will be compiled
  -h, --help               Print help

Available codemods

The compiler currently has these available codemods:

mark-dangerous-conditional-fragment-spreads

This codemod finds fragment spreads that are dangerously unaliased; that is,
the fragment might not be fetched due to a directive such as @skip or its
inclusion on a mismatched type within a union. If such a conditional fragment is
not aliased with @alias, there is no way for the
resulting generated Flow or TypeScript types to reflect its nullability. This
codemod will add the @dangerously_unaliased_fixme directive to such fragment
spreads, indicating to developers that there is a problem to be fixed. After
applying this codemod, the enforce_fragment_alias_where_ambiguous feature flag
can be enabled, which will ensure any future ambiguous fragment spreads must be
aliased.

Since this codemod can potentially modify many files, there is an optional
--rollout-percentage parameter (shorthand: -r) which, if used alongside the
enforce_fragment_alias_where_ambiguous feature flag in rollout mode, allows
progressive codemod and enforcement of this validation.

remove-unnecessary-required-directives

Removes @required
directives from non-null fields within
@throwOnFieldError
fragments and operations, or linked fields with
@catch, where the compiler is certain that the
directive does not change the generated types for the data being fetched.

fix-all

Runs all Relay compiler transforms and automatically applies fixes for every
fixable diagnostic. This is a convenient catch-all codemod to apply when
upgrading Relay versions — run it after updating the package to repair any
issues the compiler now flags:

sh
relay codemod fix-all

Guides Document Comparison (`website/docs/guides/document-comparison.mdx`)


id: document-comparison
title: Document Comparison
slug: /guides/document-comparison/
keywords:

  • compare
  • diff
  • ir

import DocsRating from '@site/src/core/DocsRating';

:::note
This feature is experimental and the API may change in future versions.
:::

The Relay compiler includes an experimental feature for comparing the intermediate representations (IR) of two GraphQL documents. This feature determines whether one document is a subset of another and reports any missing selections.

This feature is particularly useful for comparing documents generated by LLMs where exact match of LLM generated documents and static expected documents is neither attainable or desirable, due to the nature of LLMs. In those cases, you could:

  • Specify (via --left) an expected document with minimally required selections, and determine if the LLM generated document (via --right) contains all required selections, via the subset test that the command performs.
  • Optionally, pass exepected document via --right and LLM generated document via --left to determine whether LLM generated documents contains extra selections than expected, aka. overfetching.

Usage

text
> relay experimental-compare-document-ir --help
EXPERIMENTAL! Compare intermediate representations (IR) of documents passed via left and right, outputs selections that exists in left but not in right.

Usage: relay experimental-compare-document-ir [OPTIONS] --left <LEFT> --right <RIGHT>

Options:
      --left <LEFT>                    Document in string, must contain exactly 1 operation
      --right <RIGHT>                  Document in string, must contain exactly 1 operation
      --schemaPaths <SCHEMA_PATHS>...  Path(s) to the full schema file(s) to convert documents to intermediate representation (IR) for comparison
  -h, --help                           Print help

Output

Detailed information about missing selection(s) from the right document, including line number and path.

How It Works

The 2 documents (left and right) are first transformed to their Intermediate Representation (IR), then to Normalized Tree form, for comparison. Each node of the tree corresponds to a selection in the original document, and is checked for its existence in the other tree.

Normalized Trees

Each IR is transformed into a normalized tree structure that:

  • Inlines fragments: Fragment spreads and inline fragments are inlined, while type conditions specified by the fragment are encoded in the node.
  • De-aliases fields: Converts aliased fields to their original names (e.g., my_id: id → id)
  • Deduplicates fields: Removes duplicate identical fields
  • Tracks type conditions: Records which concrete object types can reach each scalar field at the leaf level

The normalized tree mirrors the JSON response structure of the query.

Tree Node Identity

Consists of

  • Path from operation root to the corresponding selection
  • Field arguments of the corresponding selection (if applicable)
  • Type condition: the type that the enclosing object must be, for the corresponding selection to be included in the response. E.g. with ... on A { id }, the type condition of selection id is A if A is a concrete type, or a set of types that are A if A is an abstract type.

Tree Node Existence

A selection exists in a tree if:

  • An identical selection is found
  • A superset of the selection is found. A selection is a superset of another if:
    • It allows the same selection and more, with query variables. E.g. user(id: $id) is a superset of user(id: 100) because variable $id can be set to arbitrary values, including 100, at runtime.
    • Its type condition is a superset of another.

Example

Left document:

graphql
query A {
  viewer {
    timezone_estimate {
      display_name
      gmt_offset
    }
  }
}

Right document:

graphql
query A {
  viewer {
    timezone_estimate {
      display_name
    }
  }
}

Output

text
[INFO] Found 1 missing selection(s):
    4 │       display_name
    5 │       gmt_offset
      │       ^^^^^^^^^^
    6 │     }
* query -> viewer -> timezone_estimate -> gmt_offset

Explore tests for more examples.


Guides Graphql Server Specification (`website/docs/guides/graphql-server-specification.mdx`)


id: graphql-server-specification
title: GraphQL Server Specification
slug: /guides/graphql-server-specification/
keywords:

  • GraphQL
  • server
  • specification

import DocsRating from '@site/src/core/DocsRating';

The goal of this document is to specify the assumptions that Relay makes about a GraphQL server and demonstrate them through an example GraphQL schema.

Table of Contents:

Preface

The two core assumptions that Relay makes about a GraphQL server are that it provides:

  1. A mechanism for refetching an object.
  2. A description of how to page through connections.

This example demonstrates all two of these assumptions. This example is not comprehensive, but it is designed to quickly introduce these core assumptions, to provide some context before diving into the more detailed specification of the library.

The premise of the example is that we want to use GraphQL to query for information about ships and factions in the original Star Wars trilogy.

It is assumed that the reader is already familiar with GraphQL; if not, the README for GraphQL.js is a good place to start.

It is also assumed that the reader is already familiar with Star Wars; if not, the 1977 version of Star Wars is a good place to start, though the 1997 Special Edition will serve for the purposes of this document.

Schema

The schema described below will be used to demonstrate the functionality that a GraphQL server used by Relay should implement. The two core types are a faction and a ship in the Star Wars universe, where a faction has many ships associated with it.

graphql
interface Node {
  id: ID!
}

type Faction implements Node {
  id: ID!
  name: String
  ships: ShipConnection
}

type Ship implements Node {
  id: ID!
  name: String
}

type ShipConnection {
  edges: [ShipEdge]
  pageInfo: PageInfo!
}

type ShipEdge {
  cursor: String!
  node: Ship
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

type Query {
  rebels: Faction
  empire: Faction
  node(id: ID!): Node
}

Object Identification

Both Faction and Ship have identifiers that we can use to refetch them. We expose this capability to Relay through the Node interface and the node field on the root query type.

The Node interface contains a single field, id, which is an ID!. The node root field takes a single argument, an ID!, and returns a Node. These two work in concert to allow refetching; if we pass the id returned in that field to the node field, we get the object back.

Let's see this in action, and query for the ID of the rebels:

graphql
query RebelsQuery {
  rebels {
    id
    name
  }
}

returns

json
{
  "rebels": {
    "id": "RmFjdGlvbjox",
    "name": "Alliance to Restore the Republic"
  }
}

So now we know the ID of the Rebels in our system. We can now refetch them:

graphql
query RebelsRefetchQuery {
  node(id: "RmFjdGlvbjox") {
    id
    ... on Faction {
      name
    }
  }
}

returns

json
{
  "node": {
    "id": "RmFjdGlvbjox",
    "name": "Alliance to Restore the Republic"
  }
}

If we do the same thing with the Empire, we'll find that it returns a different ID, and we can refetch it as well:

graphql
query EmpireQuery {
  empire {
    id
    name
  }
}

yields

json
{
  "empire": {
    "id": "RmFjdGlvbjoy",
    "name": "Galactic Empire"
  }
}

and

graphql
query EmpireRefetchQuery {
  node(id: "RmFjdGlvbjoy") {
    id
    ... on Faction {
      name
    }
  }
}

yields

json
{
  "node": {
    "id": "RmFjdGlvbjoy",
    "name": "Galactic Empire"
  }
}

The Node interface and node field assume globally unique IDs for this refetching. A system without globally unique IDs can usually synthesize them by combining the type with the type-specific ID, which is what was done in this example.

The IDs we got back were base64 strings. IDs are designed to be opaque (the only thing that should be passed to the id argument on node is the unaltered result of querying id on some object in the system), and base64ing a string is a useful convention in GraphQL to remind viewers that the string is an opaque identifier.

Complete details on how the server should behave are available in the GraphQL Object Identification best practices guide in the GraphQL site.

Connections

A faction has many ships in the Star Wars universe. Relay contains functionality to make manipulating one-to-many relationships easy, using a standardized way of expressing these one-to-many relationships. This standard connection model offers ways of slicing and paginating through the connection.

Let's take the rebels, and ask for their first ship:

graphql
query RebelsShipsQuery {
  rebels {
    name
    ships(first: 1) {
      edges {
        node {
          name
        }
      }
    }
  }
}

yields

json
{
  "rebels": {
    "name": "Alliance to Restore the Republic",
    "ships": {
      "edges": [
        {
          "node": {
            "name": "X-Wing"
          }
        }
      ]
    }
  }
}

That used the first argument to ships to slice the result set down to the first one. But what if we wanted to paginate through it? On each edge, a cursor will be exposed that we can use to paginate. Let's ask for the first two this time, and get the cursor as well:

text
query MoreRebelShipsQuery {
  rebels {
    name
    ships(first: 2) {
      edges {
        cursor
        node {
          name
        }
      }
    }
  }
}

and we get back

json
{
  "rebels": {
    "name": "Alliance to Restore the Republic",
    "ships": {
      "edges": [
        {
          "cursor": "YXJyYXljb25uZWN0aW9uOjA=",
          "node": {
            "name": "X-Wing"
          }
        },
        {
          "cursor": "YXJyYXljb25uZWN0aW9uOjE=",
          "node": {
            "name": "Y-Wing"
          }
        }
      ]
    }
  }
}

Notice that the cursor is a base64 string. That's the pattern from earlier: the server is reminding us that this is an opaque string. We can pass this string back to the server as the after argument to the ships field, which will let us ask for the next three ships after the last one in the previous result:

text
query EndOfRebelShipsQuery {
  rebels {
    name
    ships(first: 3 after: "YXJyYXljb25uZWN0aW9uOjE=") {
      edges {
        cursor
        node {
          name
        }
      }
    }
  }
}

gives us

json
{
  "rebels": {
    "name": "Alliance to Restore the Republic",
    "ships": {
      "edges": [
        {
          "cursor": "YXJyYXljb25uZWN0aW9uOjI=",
          "node": {
            "name": "A-Wing"
          }
        },
        {
          "cursor": "YXJyYXljb25uZWN0aW9uOjM=",
          "node": {
            "name": "Millennium Falcon"
          }
        },
        {
          "cursor": "YXJyYXljb25uZWN0aW9uOjQ=",
          "node": {
            "name": "Home One"
          }
        }
      ]
    }
  }
}

Sweet! Let's keep going and get the next four!

graphql
query RebelsQuery {
  rebels {
    name
    ships(first: 4 after: "YXJyYXljb25uZWN0aW9uOjQ=") {
      edges {
        cursor
        node {
          name
        }
      }
    }
  }
}

yields

json
{
  "rebels": {
    "name": "Alliance to Restore the Republic",
    "ships": {
      "edges": []
    }
  }
}

Hm. There were no more ships; guess there were only five in the system for the rebels. It would have been nice to know that we'd reached the end of the connection, without having to do another round trip in order to verify that. The connection model exposes this capability with a type called PageInfo. So let's issue the two queries that got us ships again, but this time ask for hasNextPage:

graphql
query EndOfRebelShipsQuery {
  rebels {
    name
    originalShips: ships(first: 2) {
      edges {
        node {
          name
        }
      }
      pageInfo {
        hasNextPage
      }
    }
    moreShips: ships(first: 3 after: "YXJyYXljb25uZWN0aW9uOjE=") {
      edges {
        node {
          name
        }
      }
      pageInfo {
        hasNextPage
      }
    }
  }
}

and we get back

json
{
  "rebels": {
    "name": "Alliance to Restore the Republic",
    "originalShips": {
      "edges": [
        {
          "node": {
            "name": "X-Wing"
          }
        },
        {
          "node": {
            "name": "Y-Wing"
          }
        }
      ],
      "pageInfo": {
        "hasNextPage": true
      }
    },
    "moreShips": {
      "edges": [
        {
          "node": {
            "name": "A-Wing"
          }
        },
        {
          "node": {
            "name": "Millennium Falcon"
          }
        },
        {
          "node": {
            "name": "Home One"
          }
        }
      ],
      "pageInfo": {
        "hasNextPage": false
      }
    }
  }
}

So on the first query for ships, GraphQL told us there was a next page, but on the next one, it told us we'd reached the end of the connection.

Relay uses all of this functionality to build out abstractions around connections, to make these easy to work with efficiently without having to manually manage cursors on the client.

Complete details on how the server should behave are available in the GraphQL Cursor Connections spec.

GraphQL Conf Talk

Sabrina Wasserman, an engineer at Meta, gave a talk at GraphQL Conf 2024 deriving the GraphQL connection spec, and explaining how having the behavior of list pagination specified can enable powerful tooling for clients broadly, not just Relay.

Further Reading

This concludes the overview of the GraphQL Server Specifications. For the detailed requirements of a Relay-compliant GraphQL server, a more formal description of the Relay cursor connection model, the GraphQL global object identification model are all available.

To see code implementing the specification, the GraphQL.js Relay library provides helper functions for creating nodes and connections; that repository's __tests__ folder contains an implementation of the above example as integration tests for the repository.


Guides Network Layer (`website/docs/guides/network-layer.mdx`)


id: network-layer
title: Network Layer
slug: /guides/network-layer/
keywords:

  • network
  • caching

import DocsRating from '@site/src/core/DocsRating';

In order to know how to access your GraphQL server, Relay requires developers to provide an object implementing the INetwork interface when creating an instance of a Relay Environment. The environment uses this network layer to execute queries, mutations, and (if your server supports them) subscriptions. This allows developers to use whatever transport (HTTP, WebSockets, etc) and authentication is most appropriate for their application, decoupling the environment from the particulars of each application's network configuration.

Currently the easiest way to create a network layer is via a helper from the relay-runtime package:

javascript
import {
  Environment,
  Network,
  RecordSource,
  Store,
} from 'relay-runtime';

// Define a function that fetches the results of an operation (query/mutation/etc)
// and returns its results as a Promise:
function fetchQuery(
  operation,
  variables,
  cacheConfig,
  uploadables,
) {
  return fetch('/graphql', {
    method: 'POST',
    headers: {
      // Add authentication and other headers here
      'content-type': 'application/json'
    },
    body: JSON.stringify({
      query: operation.text, // GraphQL text from input
      variables,
    }),
  }).then(response => {
    return response.json();
  });
}

// Create a network layer from the fetch function
const network = Network.create(fetchQuery);
const store = new Store(new RecordSource())

const environment = new Environment({
  network,
  store
  // ... other options
});

export default environment;

:::warning
This is just a basic example to help you get started. Features like @stream, @defer or Persisted Queries will require additional logic be added to your fetchQuery function.
:::

Advanced Network Layer

The network layer provides a place for developers to customize the data fetching behaviors. For example, adding network level caching, telemetry or support for uploading form data for mutations.


Guides Persisted Queries (`website/docs/guides/persisted-queries.mdx`)


id: persisted-queries
title: Persisted Queries
slug: /guides/persisted-queries/
keywords:

  • persisted

import DocsRating from '@site/src/core/DocsRating';
import {FbInternalOnly, OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

:::tip
At Meta, persistence is handled by the relay command for you. You likely do not need to worry about the contents of this guide.
:::

The Relay compiler supports persisted queries. This is useful because:

  • The client operation text becomes just an md5 hash which is usually shorter than the real
    query string. This saves upload bytes from the client to the server.

  • The server can now allowlist queries which improves security by restricting the operations
    that can be executed by a client.

Usage on the client

The `persistConfig` option

In the relay configuration section of your package.json you'll need specify
"persistConfig".

text
"scripts": {
  "relay": "relay-compiler",
  "relay-persisting": "node relayLocalPersisting.js"
},
"relay": {
  "src": "./src",
  "schema": "./schema.graphql",
  "persistConfig": {
    "url": "http://localhost:2999",
    "params": {}
  }
}

Specifying persistConfig in the config will do the following:

  1. It converts all query and mutation operation texts to md5 hashes.

    For example without persistConfig, a generated ConcreteRequest might look
    like below:

    javascript
    const node/*: ConcreteRequest*/ = (function(){
    //... excluded for brevity
    return {
      "kind": "Request",
      "operationKind": "query",
      "name": "TodoItemRefetchQuery",
      "id": null, // NOTE: id is null
      "text": "query TodoItemRefetchQuery(\n  $itemID: ID!\n) {\n  node(id: $itemID) {\n    ...TodoItem_item_2FOrhs\n  }\n}\n\nfragment TodoItem_item_2FOrhs on Todo {\n    text\n    isComplete\n}\n",
      //... excluded for brevity
    };
    })();

    With persistConfig this becomes:

    javascript
    const node/*: ConcreteRequest*/ = (function(){
    //... excluded for brevity
    return {
      "kind": "Request",
      "operationKind": "query",
      "name": "TodoItemRefetchQuery",
      "id": "3be4abb81fa595e25eb725b2c6a87508", // NOTE: id is now an md5 hash
      // of the query text
      "text": null, // NOTE: text is null now
      //... excluded for brevity
    };
    })();
  2. It will send an HTTP POST request with a text parameter to the
    specified url.
    You can also add additional request body parameters via the params option.

text
"scripts": {
  "relay": "relay-compiler"
},
"relay": {
  "src": "./src",
  "schema": "./schema.graphql",
  "persistConfig": {
    "url": "http://localhost:2999",
    "params": {}
  }
}

Local Persisted Queries

With the following config, you can generate a local JSON file which contains a map of operation_id => full operation text.

text
"scripts": {
  "relay": "relay-compiler"
},
"relay": {
  "src": "./src",
  "schema": "./schema.graphql",
  "persistConfig": {
    "file": "./persisted_queries.json",
    "algorithm": "MD5" // this can be one of MD5, SHA256, SHA1
  }
}

Ideally, you'll take this file and ship it to your server at deploy time so your server knows about all the queries it could possibly receive. If you don't want to do that, you'll have to implement the Automatic Persisted Queries handshake.

Tradeoffs

  • ✅ If your server's persisted query datastore gets wiped, you can recover automatically through your client's requests.
  • ❌ When there's a cache miss, it'll cost you an extra round trip to the server.
  • ❌ You'll have to ship your persisted_queries.json file to the browser which will increase your bundle size.

Example implementation of `relayLocalPersisting.js`

Here's an example of a simple persist server that will save query text to the queryMap.json file.

text
/* Detailed source-code truncated for AI context efficiency. */

The example above writes the complete query map file to ./queryMap.json.
To use this, you'll need to update package.json:

text
"scripts": {
  "persist-server": "node ./relayLocalPersisting.js",
  "relay": "relay-compiler"
}

Network layer changes

You'll need to modify your network layer fetch implementation to pass an ID parameter in the POST body (e.g., doc_id) instead of a query parameter:

javascript
function fetchQuery(operation, variables) {
  return fetch('/graphql', {
    method: 'POST',
    headers: {
      'content-type': 'application/json'
    },
    body: JSON.stringify({
      doc_id: operation.id, // NOTE: pass md5 hash to the server
      // query: operation.text, // this is now obsolete because text is null
      variables,
    }),
  }).then(response => {
    return response.json();
  });
}

Executing Persisted Queries on the Server

Your server should then look up the query referenced by doc_id when responding to this request.

To execute client requests that send persisted queries instead of query text, your server will need to be able
to lookup the query text corresponding to each ID. Typically this will involve saving the output of the queryMap.json JSON file to a database or some other storage mechanism, and retrieving the corresponding text for the ID specified by a client.

Additionally, your implementation of relayLocalPersisting.js could directly save queries to the database or other storage.

For universal applications where the client and server code are in one project, this is not an issue since you can place
the query map file in a common location accessible to both the client and the server.

Compile time push

For applications where the client and server projects are separate, one option is to have an additional npm run script
to push the query map at compile time to a location accessible by your server:

javascript
"scripts": {
  "push-queries": "node ./pushQueries.js",
  "persist-server": "node ./relayLocalPersisting.js",
  "relay": "relay-compiler && npm run push-queries"
}

Some possibilities of what you can do in ./pushQueries.js:

  • git push to your server repo.

  • Save the query maps to a database.

Run time push

A second more complex option is to push your query maps to the server at runtime, without the server knowing the query IDs at the start.
The client optimistically sends a query ID to the server, which does not have the query map. The server then in turn requests
for the full query text from the client so it can cache the query map for subsequent requests. This is a more complex approach
requiring the client and server to interact to exchange the query maps.

Simple server example

Once your server has access to the query map, you can perform the mapping. The solution varies depending on the server and
database technologies you use, so we'll just cover the most common and basic example here.

If you use express-graphql and have access to the query map file, you can import it directly and
perform the matching using the persistedQueries middleware from express-graphql-persisted-queries.

javascript
import express from 'express';
import {graphqlHTTP} from 'express-graphql';
import {persistedQueries} from 'express-graphql-persisted-queries';
import queryMap from './path/to/queryMap.json';

const app = express();

app.use(
  '/graphql',
  persistedQueries({
    queryMap,
    queryIdKey: 'doc_id',
  }),
  graphqlHTTP({schema}),
);

Using `persistConfig` and `--watch`

It is possible to continuously generate the query map files by using the persistConfig and --watch options simultaneously.
This only makes sense for universal applications i.e. if your client and server code are in a single project
and you run them both together on localhost during development. Furthermore, in order for the server to pick up changes
to the queryMap.json, you'll need to have server side hot-reloading set up. The details on how to set this up
are out of the scope of this document.


Guides Required Directive (`website/docs/guides/required-directive.mdx`)


id: required-directive
title: "@required Directive"
slug: /guides/required-directive/
keywords:

  • required
  • directive
  • optional
  • nullthrows

import DocsRating from '@site/src/core/DocsRating';

The @required directive can be added to fields in your Relay queries to declare how null values should be handled at runtime. You can think of it as saying "if this field is ever null, its parent field is invalid and should be null".

When you have a GraphQL schema where many fields are nullable, a considerable amount of product code is needed to handle each field's potential "nullness" before the underlying data can be used. With @required, Relay can handle some types of null checks before it returns data to your component, which means that any field you annotate with @required will become non-nullable in the generated types for your response.

If a @required field is null at runtime, Relay will "bubble" that nullness up to the field's parent. For example, given this query:

graphql
query MyQuery {
  viewer {
    name @required(action: LOG)
    age
  }
}

If name is null, Relay would return { viewer: null }. You can think of @required in this instance as saying "viewer is useless without a name".

Action

The @required directive has a required action argument which has four possible values:

`NONE` (expected)

This field is expected to be null sometimes. The field's nullability will "bubble up" to make the parent field null if this field is missing, allowing the component to check the parent field instead of this specific field.

`LOG` (recoverable)

This value is not expected to ever be null, but the component can still render if it is. If a field with action: LOG is null, the Relay field logger will receive a missing_required_field.log event. The field's nullability will "bubble up" to make the parent field null.

`THROW` (unrecoverable)

This value should not be null, and the component cannot render without it. If a field with action: THROW is null at runtime, the component which reads that field will throw during render. The error message includes both the owner and field path. Only use this option if your component is contained within an error boundary.

Note: If you have opted into the optional disallow_required_action_throw_on_semantically_nullable_fields compiler feature flag, this can only be used on fields that are non-nullable (! in the schema) or are marked with @semanticNonNull (an optional semantic nullability feature that indicates fields should never be null in normal operation).

`DANGEROUSLY_THROW_ON_SEMANTICALLY_NULLABLE_FIELD` (unrecoverable, bypass validation)

:::warning
This option is included mostly as a migration path for existing codebases that used @required(action: THROW) on nullable fields. It is strongly discouraged to add new usages of this option.
:::

This action behaves identically to THROW but can be used on nullable fields. This is only needed when the optional compiler feature flag disallow_required_action_throw_on_semantically_nullable_fields is enabled, which prevents using THROW on semantically nullable fields.

Locality

A field's @required status is local to the fragment where it is specified. This allows you to add/remove the directive without having to think about anything outside the scope of your component.

This choice reflects the fact that some components may be able to recover better from missing data than others. For example, a <RestaurantInfo /> component could probably render something sensible even if the restaurant's address is missing, but a <RestaurantLocationMap /> component might not.

However, all usages of the @required directive on the same field in a single fragment must be consistent with their usage. This situation mostly occurs when selecting fields in inline fragments. For example, the following fragment would fail to compile:

graphql
fragment UserInfo on User {
  job {
    ... on Actor {
      certifications
    }
    ... on Lawyer {
      certifications @required(action: LOG)
    }
  }
}

The Relay compiler will give you an error like All references to a field must have matching @required declarations.. To fix this, either set the @required directive on each of the fields selected in the inline fragment or remove the directive entirely.

Chaining

@required directives can be chained to make a deeply nested field accessible after just one null check:

javascript
const user = useFragment(graphql`
  fragment MyUser on User {
    name @required(action: LOG)
    profile_picture @required(action: LOG) {
      url @required(action: LOG)
    }
  }`, key);
 if(user == null) {
   return null;
 }
 return <img src={user.profile_picture.url} alt={user.name} />

Note: If you use @required on a top level field of a fragment, the object returned from useFragment itself may become nullable. The generated types will reflect this.

When chaining @required directives, the Relay compiler will help you from unintentionally creating a chain with a more severe action than intended. Consider the following fragment

graphql
fragment MyUser on User {
  profile_picture @required(action: THROW) {
    url @required(action: LOG)
  }
}

In this example we want the component to THROW if the profile_picture field is null but we only want to LOG an error if the url field is null. But recall, Relay will "bubble" nullness up to the parent field, if the url field is null it will then cause the profile_picture field to become null as well. And once that happens, the component will THROW. If you implement a pattern like this, the Relay compiler will give you an error

text
A @required field may not have an `action` less severe than that of its @required parent. This @required directive should probably have `action: LOG` so that it can match its parent

To fix this, either change the profile_picture to use action: LOG or change the url field to use action: THROW.

Caveats with Connections

There are currently some limitations in using the @required and @connection directives together. When you use the @connection directive, Relay automatically inserts some additional fields into the connection, and those fields won't be generated with the @required directive. This can result in inconsistencies if you use the @required directive on fields in a Connection type. Consider the following example:

graphql
fragment FriendsList on User @refetchable(queryName: "FriendsListQuery") {
  friends(after: $cursor, first: $count) @connection(key: "FriendsList_friends") {
    edges {
      node @required(action: LOG) {
        job @required(action: LOG) {
          title @required(action: LOG)
        }
      }
    }
  }
}

Any usages of @required on the node field or any of its direct child fields will cause the Relay compiler to give you an error saying All references to a field must have matching @required declarations.. In order to bypass this you'll need to remove the @required directives on those fields.

In the above example, we'd need to remove the @required directives on both the node and job fields, but the usage on the title field would not create an error.

graphql
fragment FriendsList on User @refetchable(queryName: "FriendsListQuery") {
  friends(after: $cursor, first: $count) @connection(key: "FriendsList_friends") {
    edges {
      node {
        job {
          title @required(action: LOG)
        }
      }
    }
  }
}

FAQ

Why did @required make a non-nullable field/root nullable?

When using the LOG or NONE actions, Relay will "bubble" a missing field up to its parent field or fragment root. This means that adding @required(action: LOG) (for example) to a child of a non-nullable fragment root will cause the type of the fragment root to become nullable.

What happens if you use `@required` in a plural field

If a @required(action: LOG) field is missing in a plural field, the item in the list will be returned as null. It will not cause the entire array to become null.. If you have any question about how it will behave, you can inspect the generated Flow types.

Why are @required fields in an inline fragment still nullable?

Imagine a fragment like this:

graphql
fragment MyFrag on Actor {
  ... on User {
    name @required(action: THROW)
  }
}

It's possible that your Actor will not be a User and therefore not include a name. To represent that in types, we generate a Flow type that looks like this: {name?: string}.

If you encounter this issue, you can add a __typename like this:

graphql
fragment MyFrag on Actor {
  __typename
  ... on User {
    name @required(action: THROW)
  }
}

In this situation Relay will generate a union type like: {__typename: 'User', name: string} | {__typename: '%ignore this%}. Now you can check the __typename field to narrow your object's type down to one that has a non-nullable name.

Why not implement this at the schema/server level?

The "requiredness" of a field is actually a product decision and not a schema question. Therefore we need to implement the handling of it at the product level. Individual components need to be able to decide for themselves how to handle a missing value.

For example, if a notification is trying to show the price for a Marketplace listing, it could probably just omit the price and still render. If payment flow for that same listing is missing the price, it should probably blow up.

Another issue is that changes to the server schema are much more difficult to ship since they affect all existing clients across all platforms.

Basically every value returned by Relay is nullable. This is intentional since we want to be able to handle field-level errors whenever possible. If we lean into KillsParentOnException we would end up wanting to make basically every field use it and our apps would be becomes more brittle since errors which used to be small, become large.

Extracted from this comment thread.
Further discussion in this comment thread.

Can `(action: NONE)` be the default?

On one hand action: NONE makes the most sense as a default (omitted action == no action). However, we are aware that whichever value we choose as the default will be considered the default action for engineers to choose since it's the path of least resistance.

We actually believe that in most cases LOG is the most ideal choice. It gives the component a chance to gracefully recover while also giving us signal that a part of our app is rendering in a sub-optimal way.

We debated making LOG the default action for that reason, but I think that's confusing as well.

So, for now we are planning to not offer a default argument. After all, it's still much less to write out than the equivalent manual null checks. Once we see how people use it we will consider what value (if any) should be the default.


Guides Semantic Nullability (`website/docs/guides/semantic-nullability.mdx`)


id: semantic-nullability
title: "Semantic Nullability"
slug: /guides/semantic-nullability/
keywords:

  • "null"
  • nullability
  • semanticNonNull

Motivation

One of GraphQL's strengths is its field-granular error handling which can dramatically improve response resiliency. However, today that error handling depends upon field nullability, which is the reason it is a recommended best practice to default all fields to being nullable. This creates a trade-off where enabling maximum resiliency means client developers must manually handle all possible permutations of field nullability within their components. @required can help a bit, but is ultimately a very blunt tool.

Proposed Solution

Semantic Nullability is an early GraphQL spec proposal that aims to decouple error handling and nullability in the GraphQL spec to enable maximum resiliency while still exposing the "semantic nullability", (the nullability of the actual resolver function/method on the server) of the field to the client.

The proposal works by allowing the schema to specify a new type of nullability of "null only on error". If a client sees this type, and the client has some strategy for handling field errors out-of-band, it may treat the field that is exposed to user code as non-nullable.

The full spec change will likely require adding additional syntax to GraphQL's schema definition language, but in the meantime, various GraphQL servers and clients have collaborated on a temporary directive @semanticNonNull that can be used to experiment with this idea.

In short, you can add @semanticNonNull to a field in your schema to indicate that the field is non-nullable in the semantic sense, but that the client should still be prepared to handle errors.

Relay will look for @semanticNonNull directives in your schema and generate non-nullable Flow/TypeScript types for those fields if you enable client-side error handling for your query or fragment by adding the @throwOnFieldError directive.

If your server will never return null for a user's name, except in the case of errors, for example because it's resolver is typed as non-nullable, you can then apply @semanticNonNull to that field in your schema.

graphql title="schema.graphql"
directive @semanticNonNull(levels: [Int] = [0]) on FIELD_DEFINITION

type User {
  name: String @semanticNonNull
}

Once you've added the directive to your schema, you can add @throwOnFieldError to your fragment and queries to indicate that the client should throw an error if any field errors are encountered when the fragment is read.

:::note
Be sure to add React error boundaries to your app above any components that are using @throwOnFieldError.
:::

In the below example, Relay's generated TypeScript or Flow types for user.name will be non-nullable.

:::caution
If Relay receives a field error for user.name, useFragment will throw an error. For this reason, it's important to ensure that you are have adequate React error boundaries in place to catch these errors.
:::

js
import type {UserComponent_user$key} from 'UserComponent_user.graphql';

const React = require('React');
const {graphql, useFragment} = require('react-relay');

type Props = {
  user: UserComponent_user$key,
};

function UserComponent(props: Props) {
  const user = useFragment(
    graphql`
      fragment UserComponent_user on User @throwOnFieldError {
        name # Will be typed as non-nullable
      }
    `,
    props.user,
  );

  return <div>{user.name}</div>
}

By Example

For a hands on example, see this example project showing Relay configured to use @semanticNonNull and @throwOnFieldError alongside Grats which has support for automatically deriving a schema that includes the experimental @semanticNonNull directives.

GraphQL Conf Talk

The Relay team gave a talk at GraphQL Conf 2024 about semantic nullability. You can watch it here:

Further Reading


Guides Testing Relay Components (`website/docs/guides/testing-relay-components.mdx`)


id: testing-relay-components
title: Testing Relay Components
slug: /guides/testing-relay-components/
keywords:

  • testing
  • createMockEnvironment
  • RelayMockEnvironment
  • MockPayloadGenerator
  • relay_test_operation
  • queuePendingOperation
  • resolver

import DocsRating from '@site/src/core/DocsRating';
import {FbInternalOnly, OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

Abstract

The purpose of this document is to cover the Relay APIs for testing Relay components.

The content is focused mostly on jest unit-tests (testing individual components) and integration tests (testing a combination of components). But these testing tools may be applied in different cases: screenshot-tests, production smoke-tests, "Redbox" tests, fuzz-tests, e2e test, etc.

What are the benefits of writing jest tests:

  • In general, it improves the stability of the system. Flow helps with catching a various set of Javascript errors, but it is still possible to introduce regressions to the components. Unit-tests help find, reproduce, and fix regressions, and prevent them in the future.
  • It simplifies the refactoring process: when properly written (testing public interface, not implementation) - tests help with changing the internal implementation of the components.
  • It may speed up and improve the development workflow. Some people may call it Test Driven Development (TM). But essentially it's just writing tests for public interfaces of your components, and then writing the components that implement those interfaces. Jest —watch mode really shines in this case.
  • It will simplify the on-boarding process for new developers. Having tests helps new developers ramp up on the new code base, allowing them to fix bugs and deliver features.

One thing to notice: while jest unit- and integration tests will help improve the stability of the system, they should be considered one part of a bigger stability infrastructure with multiple layers of automated testing: flow, e2e, screenshot, "Redbox", performance tests.

Testing with Relay

Testing applications that use Relay may be challenging, because of the additional data fetching layer that wraps the actual product code.

And it's not always easy to understand the mechanics of all processes that are happening behind Relay, and how to properly handle interactions with the framework.

Fortunately, there are tools that aim to simplify the process of writing tests for Relay components, by providing imperative APIs for controlling the request/response flow and additional API for mock data generation.

There are two main Relay modules that you may use in your tests:

  • createMockEnvironment(options): RelayMockEnvironment
  • MockPayloadGenerator and the @relay_test_operation directive

With createMockEnvironment, you will be able to create an instance of RelayMockEnvironment, a Relay environment specifically for your tests. The instance created by createMockEnvironment implements the Relay Environment Interface and it also has an additional Mock layer, with methods that allow you to resolve/reject and control the flow of operations (queries/mutations/subscriptions).

The main purpose of MockPayloadGenerator is to improve the process of creating and maintaining the mock data for tested components.

One of the patterns you may see in the tests for Relay components: 95% of the test code is the test preparation—the gigantic mock object with dummy data, manually created, or just a copy of a sample server response that needs to be passed as the network response. And the remaining 5% is actual test code. As a result, people don't test much. It's hard to create and manage all these dummy payloads for different cases. Hence, writing tests is time-consuming and tests are sometimes painful to maintain.

With the MockPayloadGenerator and @relay_test_operation, we want to get rid of this pattern and switch the developer's focus from the preparation of the test to the actual testing.

Testing with React and Relay

React Testing Library is a set of helpers that let you test React components without relying on their implementation details. This approach makes refactoring a breeze and also nudges you towards best practices for accessibility. Although it doesn't provide a way to "shallowly" render a component without its children, a test runner like Jest lets you do this by mocking.

Note: The ReactTestRenderer library has been deprecated since React v18. ReactTestRenderer may still be referenced internally as an alternative to React Testing Library. However, when possible, we recommend using React Testing Library (or @testing-library/react-native) to test your React applications with Relay.

RelayMockEnvironment API Overview

RelayMockEnvironment is a special version of Relay Environment with additional API methods for controlling the operation flow: resolving and rejection operations, providing incremental payloads for subscriptions, working with the cache.

  • Methods for finding operations executed on the environment
    • getAllOperations() - get all operation executed during the test by the current time
    • findOperation(findFn => boolean) - find particular operation in the list of all executed operations, this method will throw, if operation is not available. Maybe useful to find a particular operation when multiple operations executed at the same time
    • getMostRecentOperation() - return the most recent operation, this method will throw if no operations were executed prior this call.
  • Methods for resolving or rejecting operations
    • nextValue(request | operation, data) - provide payload for operation(request), but not complete request. Practically useful when testing incremental updates and subscriptions
    • complete(request | operation) - complete the operation, no more payloads are expected for this operation, when it's completed.
    • resolve(request | operation, data) - resolve the request with provided GraphQL response. Essentially, it's nextValue(...) and complete(...)
    • reject(request | operation, error) - reject the request with particular error
    • resolveMostRecentOperation(operation => data) - resolve and getMostRecentOperation work together
    • rejectMostRecentOperation(operation => error) - reject and getMostRecentOperation work together
    • queueOperationResolver(operation => data | error) - adds an OperationResolver function to the queue. The passed resolver will be used to resolve/reject operations as they appear
    • queuePendingOperation(query, variables) - in order for the usePreloadedQuery hook to not suspend, one must call these functions:
      • queueOperationResolver(resolver)
      • queuePendingOperation(query, variables)
      • preloadQuery(mockEnvironment, query, variables) with the same query and variables that were passed to queuePendingOperation. preloadQuery must be called after queuePendingOperation.
  • Additional utility methods
    • isLoading(request | operation) - will return true if operations has not been completed, yet.
    • cachePayload(request | operation, variables, payload) - will add payload to QueryResponse cache
    • clearCache() - will clear QueryResponse cache

Mock Payload Generator and the `@relay_test_operation` Directive

MockPayloadGenerator may drastically simplify the process of creating and maintaining mock data for your tests. MockPayloadGenerator can generate dummy data for the selection that you have in your operation. There is an API to modify the generated data - Mock Resolvers. With Mock Resolvers, you may adjust the data for your needs. Mock Resolvers are defined as an object where keys are names of GraphQL types (ID, String, User, Comment, etc), and values are functions that return the default data for the type.

Example of a simple Mock Resolver:

js
{
  ID() {
    // Return mock value for a scalar filed with type ID
    return 'my-id';
  },
  String() {
    // Every scalar field with type String will have this default value
    return "Lorem Ipsum"
  }
}

It is possible to define more resolvers for Object types

js
{
  // This will be the default values for User object in the query response
  User() {
    return {
      id: 4,
      name: "Mark",
      profile_picture: {
        uri: "http://my-image...",
      },
    };
  },
}

Mock Resolver Context

The first argument of the MockResolver is the object that contains Mock Resolver Context. It is possible to return dynamic values from mock resolvers based on the context - for instance, name or alias of the field, a path in the selection, arguments, or parent type.

js
{
  String(context) {
    if (context.name === 'zip') {
      return '94025';
    }
    if (context.path != null && context.path.join('.') === 'node.actor.name') {
      return 'Current Actor Name';
    }
    if (context.parentType === 'Image' && context.name === 'uri') {
       return 'http://my-image.url';
    }
  }
}

ID Generation

The second argument of the Mock Resolver is a function that will generate a sequence of integers, useful to generate unique ids in the tests

js
{
  // will generate strings "my-id-1", "my-id-2", etc.
  ID(_, generateId) {
     return `my-id-${generateId()}`;
  },
}

Float, Integer, Boolean, etc...

Please note, that for production queries we don't have full type information for Scalar fields - like Boolean, Integer, Float. And in the MockResolvers, they map to String. You can use context to adjust return values, based on the field name, alias, etc.

@relay_test_operation

Most of GraphQL type information for a specific field in the selection is not available during Relay runtime. By default, Relay, cannot get type information for a scalar field in the selection, or an interface type of the object.

Operation with the @relay_test_operation directive will have additional metadata that will contain GraphQL type info for fields in the operation's selection. And it will improve the quality of the generated data. You also will be able to define Mock resolvers for Scalar (not only ID and String) and Abstract types:

javascript
{
  Float() {
    return 123.456;
  },
  Boolean(context) {
    if (context.name === 'can_edit') {
      return true;
    }
    return false;
  },
  Node() {
    return {
      __typename: 'User',
      id: 'my-user-id',
    };
  }
}

Examples

Relay Component Test

Using createMockEnvironment and MockPayloadGenerator allows writing concise tests for components that use Relay hooks. Both those modules can be imported from relay-test-utils

text
/* Detailed source-code truncated for AI context efficiency. */

Component Tests With Deferred Fragments

When using MockPayloadGenerator to generate data for a Query that has fragments with @defer you may want to generate the deferred data as well. To do so, you can use MockPayloadGenerator.generateWithDefer passing the option generateDeferredPayload:

text
/* Detailed source-code truncated for AI context efficiency. */

Fragment Component Tests

Essentially, in the example above, resolveMostRecentOperation will generate data for all child fragment containers (pagination, refetch). But, usually the root component may have many child fragment components and you may want to exercise a specific component that uses useFragment. The solution for that would be to wrap your fragment container with the useLazyLoadQuery component that renders a Query that spreads fragments from your fragment component:

javascript
test('Fragment', () => {
  const environment = createMockEnvironment();
  const TestRenderer = () => {
    const data = useLazyLoadQuery(
      graphql`
        query TestQuery @relay_test_operation {
          myData: node(id: "test-id") {
            # Spread the fragment you want to test here
            ...MyFragment
          }
        }
      `,
      {},
    );
    return <MyFragmentComponent myData={data.myData} />
  };

  const renderer = render(
    <RelayEnvironmentProvider environment={environment}>
      <Suspense fallback="Loading...">
        <TestRenderer />
      </Suspense>
    </RelayEnvironmentProvider>
  );

  // Wrapping in act will ensure that components
  // are fully updated to their final state.
  act(() => {
    environment.mock.resolveMostRecentOperation(operation =>
      MockPayloadGenerator.generate(operation),
    );
  });

  expect(renderer).toMatchSnapshot();
});

Pagination Component Test

Essentially, tests for pagination components (e.g. using usePaginationFragment) are not different from fragment component tests. But we can do more here, we can actually see how the pagination works - we can assert the behavior of our components when performing pagination (load more, refetch).

text
/* Detailed source-code truncated for AI context efficiency. */

Refetch Component

We can use similar approach here with wrapping the component with a query. And for the sake of completeness, we will add an example here:

js
test('Refetch Container', async () => {
  const environment = createMockEnvironment();
  const TestRenderer = () => {
    const data = useLazyLoadQuery(
      graphql`
        query TestQuery @relay_test_operation {
          myData: node(id: "test-id") {
            # Spread the pagination fragment you want to test here
            ...MyRefetchableFragment
          }
        }
      `,
      {},
    );
    return <MyRefetchContainer data={data.myData} />
  };

  const renderer = render(
    <RelayEnvironmentProvider environment={environment}>
      <Suspense fallback="Loading...">
        <TestRenderer />
      </Suspense>
    </RelayEnvironmentProvider>
  );

  act(() => {
    environment.mock.resolveMostRecentOperation(operation =>
      MockPayloadGenerator.generate(operation),
    );
  });

  // Assuming we have refetch button in the Container
  const refetchButton = await renderer.findByTestId('refetch');

  // This should trigger the `refetch`
  refetchButton.props.onClick();

  act(() => {
    environment.mock.resolveMostRecentOperation(operation =>
      MockPayloadGenerator.generate(operation, {
        // We can customize mock resolvers, to change the output of the refetch query
      }),
    );
  });

  expect(renderer).toMatchSnapshot();
});

Mutations

Mutations themselves are operations, so we can test them independently (unit-test) for a specific mutation, or in combination with the view from which this mutation is called.

:::note
the useMutation API is an improvement over calling commitMutation directly.
:::

js
// Say, you have a mutation function
function sendMutation(environment, onCompleted, onError, variables)
  commitMutation(environment, {
    mutation: graphql`...`,
    onCompleted,
    onError,
    variables,
  });
}

// Example test may be written like so
test('it should send mutation', () => {
  const environment = createMockEnvironment();
  const onCompleted = jest.fn();
  sendMutation(environment, onCompleted, jest.fn(), {});
  const operation = environment.mock.getMostRecentOperation();

  act(() => {
    environment.mock.resolve(
      operation,
      MockPayloadGenerator.generate(operation)
    );
  });

  expect(onCompleted).toBeCalled();
});

Subscription

:::tip
The useSubscription API is an improvement over calling requestSubscription directly.
:::

We can test subscriptions similarly to how we test mutations.

js
// Example subscribe function
function subscribe(environment, onNext, onError, variables)
  requestSubscription(environment, {
    subscription: graphql`...`,
    onNext,
    onError,
    variables,
  });
}

// Example test may be written like so
test('it should subscribe', () => {
  const environment = createMockEnvironment();
  const onNext = jest.fn();
  subscribe(environment, onNext, jest.fn(), {});
  const operation = environment.mock.getMostRecentOperation();

  act(() => {
    environment.mock.nextValue(
      operation,
      MockPayloadGenerator.generate(operation)
    );
  });

  expect(onNext).toBeCalled();
});

Example with `queueOperationResolver`

With queueOperationResolver it is possible to define responses for operations that will be executed on the environment

javascript
// Say you have a component with the QueryRenderer
const MyAwesomeViewRoot = require('MyAwesomeViewRoot');
const {
  createMockEnvironment,
  MockPayloadGenerator,
} = require('relay-test-utils');

test('Data Render', async () => {
  const environment = createMockEnvironment();
  environment.mock.queueOperationResolver(operation =>
    MockPayloadGenerator.generate(operation),
  );

  const renderer = render(
    <MyAwesomeViewRoot environment={environment} />,
  );

  // At this point operation will be resolved
  // and the data for a query will be available in the store
  expect(await renderer.findByTestId('myButton')).toBeDefined();
});

test('Error State', async () => {
  const environment = createMockEnvironment();
  environment.mock.queueOperationResolver(() =>
    new Error('Uh-oh'),
  );
  const renderer = render(
    <MyAwesomeViewRoot environment={environment} />,
  );

  expect(await renderer.findByTestId('myButton')).toBeDefined();
});

With Relay Hooks

The examples in this guide should work for testing components both with Relay Hooks, Containers or Renderers. When writing tests that involve the usePreloadedQuery hook, please also see the queuePendingOperation note above.

toMatchSnapshot(...)

Even though in all of the examples here you can see assertions with toMatchSnapshot(), we keep it that way just to make examples concise. But it's not the recommended way to test your components.

More Examples

As a reference implementation I've put working examples here:
https://phabricator.internmc.facebook.com/diffusion/FBS/browse/master/xplat/js/RKJSModules/Libraries/Relay/oss/relay-test-utils/__tests__/RelayMockEnvironmentWithComponents-test.js

The best source of example tests is in the relay-experimental package.

Testing is good. You should definitely do it.


Guides Testing Relay With Preloaded Queries (`website/docs/guides/testing-relay-with-preloaded-queries.mdx`)


id: testing-relay-with-preloaded-queries
title: Testing Relay with Preloaded Queries
slug: /guides/testing-relay-with-preloaded-queries/
keywords:

  • testing
  • preloaded
  • usePreloadedQuery
  • queueOperationResolver
  • queuePendingOperation

import DocsRating from '@site/src/core/DocsRating';
import {FbInternalOnly, OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

Components that use preloaded queries (useQueryLoader and usePreloadedQuery hooks) require slightly different and more convoluted test setup.

In short, there are two steps that need to be performed before rendering the component

  1. Configure the query resolver to generate the response via environment.mock.queueOperationResolver
  2. Record a pending queue invocation via environment.mock.queuePendingOperation

Symptoms that something is wrong

  1. The test doesn't do what is expected from it.
  2. The query seems to be blocking instead of executing
    1. E.g. the Suspend doesn't switch from "waiting" to "data loaded" state
  3. If you add the console.log before and after usePreloadedQuery, only the "before" call is hit

TL;DR

javascript
const {RelayEnvironmentProvider} = require('react-relay');
const {MockPayloadGenerator, createMockEnvironment} = require('relay-test-utils');
const {act, render} = require('@testing-library/react');
test("...", () => {
  // arrange
  const environment = createMockEnvironment();
  environment.mock.queueOperationResolver(operation => {
      return MockPayloadGenerator.generate(operation, {
        CurrencyAmount() {
          return {
            formatted_amount: "1234$",
          };
        },
      });
    });
  const query = YourComponentGraphQLQueryGoesHere; // can be the same, or just identical
  const variables = {
    // ACTUAL variables for the invocation goes here
  };
  environment.mock.queuePendingOperation(YourComponentGraphQLQuery, variables);

 // act
  const {getByTestId, ..otherStuffYouMightNeed} = render(
    <RelayEnvironmentProvider environment={environment}>
        <YourComponent data-testid="1234" {...componentPropsIfAny}/>
    </RelayEnvironmentProvider>
  );
  // trigger the loading - click a button, emit an event, etc. or ...
  act(() => jest.runAllImmediates()); // ... if loadQuery is in the useEffect()
  // assert
  // your assertions go here
});

Configure the query resolver to generate the response

This is done via environment.mock.queueOperationResolver(operation) call, but getting it right might be tricky.

The crux of this call is to return a mocked graphql result in a very particular format (as MockResolvers type, to be precise). This is done via a second parameter to generate - it is an object, whose keys are GraphQL types that we want to mock. (See mock-payload-generator).

Continuing on the above example:

js
return MockPayloadGenerator.generate(operation, {
  CurrencyAmount() { // <-- the GraphQL type
    return {
      formatted_amount: "response_value" <-- CurrencyAmount fields, selected in the query
    };
  }
});

The tricky thing here is to obtain the name of the GraphQL type and fields to return. This can be done in two ways:

  • Call console.log(JSON.stringify(operation, null, 2)) and look for the concreteType that corresponds to what we want to mock. Then look at the sibling selections array, which describes the fields that are selected from that object.
  • This is somewhat intense - P139017123 is the output for this query. Rule of thumb - one nested call in the query produces one nested object in the output.
  • Look up the type in the graphiql (bunnylol graphiql), then specify the fields listed on the query.

:::note
The type you need seems to be the type returned by the innermost function call (or calls, if you have multiple functions called in one query - see D23078476). This needs to be confirmed - in both example diffs the target types was also leafs.
:::

It is possible to return different data for different query variables via Mock Resolver Context. The query variables will be available on the context.args, but only to the innermost function call (for the query above, only offer_ids are available)

javascript
CurrencyAmount(context) {
  console.log(JSON.stringify(context, null, 2)); // <--
  return { formatted_amount: mockResponse }
}
// <-- logs { ...snip..., "name": "subtotal_price_for_offers", args: { offer_ids: [...] } }

Record a pending queue invocation

This is more straightforward - it is done via a call to environment.mock.queuePendingOperation(query, variables)

  • Query needs to match the query issues by the component. Simplest (and most robust against query changes) is to export the query from the component module and use it in the test, but having an identical (but not the same) query works as well.
  • variables has to match the variables that will be used in this test invocation.
    • Beware of nested objects and arrays - they are compared via areEqual (invocation code)
      • Arrays are compared by values (not by reference), but the order of elements matter
      • Nested objects - performs deep compare, order of keys is not relevant (this is not confirmed - please update this doc if you used a graphql query with "deep" structure*)*

Example diffs

Troubleshooting

  • console.log, console.log everywhere! Recommended places:
    • component: before and after useQueryLoader, usePreloadedQuery, loadQuery
    • test: in queueOperationResolver callback
    • library: in RelayModernMockEnvironment.execute, after the const currentOperation = ... call (here)
  • If loadQuery is not called - make sure to issue the triggering event. Depending on your component implementation it could be a user-action (like button click or key press), javascript event (via event emitter mechanisms) or a simple "delayed execution" with useEffect.
    • The useEffect case is probably easiest to miss - make sure to call act(() => jest.runAllImmediates()) after rendering the component
  • If "before" usePreloadedQuery is hit, but "after" is not - the query suspends. This entire guide is written to resolve it - you might want to re-read it. But most likely it is either:
    • Used a different query - the query resolver would not be called, currentOperation will be null
    • Query variables don't match - the query resolver would not be called, currentOperation will be null (make sure to inspect the variables).
      • Also, make sure arrays are in the same order, if any (or better yet, use sets, if at all possible).
  • If data returned from the query is not what you expect, make sure you're generating the right graphql type.
    • You can tell you're mocking the wrong one if the return values look something like <mock-value-for-field-"formatted_amount">

:::note
Make sure the component and the test use the same environment (i.e. there's no <RelayEnvironmentProvider environment={RelayFBEnvironment}> somewhere nested in your test React tree.
:::

Epilogue

Examples here use testing-library-react, but it works with the react-test-renderer as well.

See D23078476.


Guides Throw On Field Error Directive (`website/docs/guides/throw-on-field-error-directive.mdx`)


id: throw-on-field-error-directive
title: '@throwOnFieldError Directive'
slug: /guides/throw-on-field-error-directive/
keywords:

  • directive
  • optional
  • errors

import DocsRating from '@site/src/core/DocsRating';

:::tip
Both @catch and @throwOnFieldError only handle field errors in the
query/fragment/mutation in which they are used. They do not handle errors
related to fields in any spread fragments.
:::

The @throwOnFieldError directive can be added to fragments and queries. When
this directive is used, the Relay runtime will throw an exception if a field
with a field error is encountered while reading the fragment or query, or if
Relay is missing data due to a
graph relationship change.

In addition to causing the Relay runtime to throw an exception if a field error
is encountered, the @throwOnFieldError directive also enables generation of
non-null Flow types for fields that have the @semanticNonNull directive in the
schema. This means that if a field has the @semanticNonNull directive, the
generated Flow type for that field will be non-nullable; if an error were to
occur while reading that field, the thrown exception will prevent your
application from receiving a null value. Making a previously-nullable field
non-null may make many existing @required directives unnecessary; you can use
the
remove-unnecessary-required-directives codemod
to clean these up.

To use the @throwOnFieldError directive, add it to a fragment or query in your
Relay code. For example:

text
fragment MyFragment on User @throwOnFieldError {
  id
  name
}

In this example, the @throwOnFieldError directive is added to the MyFragment
fragment. If any of the fields in this fragment (in this case, id and name) have
a field error, the Relay runtime will throw an exception at the time the
fragment is read.

If you wish to handle a specific field error locally within your
@throwOnFieldError fragment or query instead of having that error throw, you
can catch the error with @catch.

Read more about Relay's experimental support for
Semantic Nullability.


Guides Type Emission (`website/docs/guides/type-emission.mdx`)


id: type-emission
title: Type Emission
slug: /guides/type-emission/
keywords:

  • type emission

import DocsRating from '@site/src/core/DocsRating';
import {FbInternalOnly, OssOnly, fbContent} from 'docusaurus-plugin-internaldocs-fb/internal';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

As part of its normal work, the Relay Compiler will emit type information for your language of choice that helps you write type-safe application code. These types are included in the artifacts that relay-compiler generates to describe your operations and fragments.

Operation variables

The shape of the variables object used for query, mutation, or subscription operations.

In this example the emitted type-information would require the variables object to contain an artistID key with a non-null string.

<Tabs
defaultValue={fbContent({internal: 'Flow', external: 'TypeScript'})}
values={[
{label: 'Flow', value: 'Flow'},
{label: 'TypeScript', value: 'TypeScript'},
]}>

javascript
/**
 * export type ExampleQuery$variables = {
 *   +artistID: string,
 * }
 * export type ExampleQuery$data = {
 *   +artist: {
 *     +name: ?string,
 *   }
 * }
 * export type ExampleQuery = {
 *   +variables: ExampleQuery$variables,
 *   +response: ExampleQuery$data,
 * }
 */

const data = useLazyLoadQuery(
  graphql`
    query ExampleQuery($artistID: ID!) {
      artist(id: $artistID) {
        name
      }
    }
  `,
  // variables are expected to be of type ExampleQuery$variables
  {artistID: 'banksy'},
);
javascript
/**
 * export type ExampleQuery$variables = {
 *   readonly artistID: string
 * }
 * export type ExampleQuery$data = {
 *   readonly artist?: {
 *     readonly name?: string
 *   }
 * }
 * export type ExampleQuery = {
 *   readonly variables: ExampleQuery$variables
 *   readonly response: ExampleQuery$data
 * }
 */
const data = useLazyLoadQuery(
  graphql`
    query ExampleQuery($artistID: ID!) {
      artist(id: $artistID) {
        name
      }
    }
  `,
  // variables are expected to be of type ExampleQuery$variables
  {artistID: 'banksy'},
);

Operation and fragment data

The shape of the data selected in a operation or fragment, following the data-masking rules. That is, excluding any data selected by fragment spreads.

In this example the emitted type-information describes the response data which is returned by useLazyLoadQuery (or usePreloadedQuery).

<Tabs
defaultValue={fbContent({internal: 'Flow', external: 'TypeScript'})}
values={[
{label: 'Flow', value: 'Flow'},
{label: 'TypeScript', value: 'TypeScript'},
]}>

javascript
/**
 * export type ExampleQuery$variables = {
 *   +artistID: string,
 * }
 * export type ExampleQuery$data = {
 *   +artist: {
 *     +name: ?string,
 *   }
 * }
 * export type ExampleQuery = {
 *   +variables: ExampleQuery$variables,
 *   +response: ExampleQuery$data,
 * }
 */

// data is of type ExampleQuery$data
const data = useLazyLoadQuery(
  graphql`
    query ExampleQuery($artistID: ID!) {
      artist(id: $artistID) {
        name
      }
    }
  `,
  {artistID: 'banksy'},
);

return props.artist && <div>{props.artist.name} is great!</div>
javascript
/**
 * export type ExampleQuery$variables = {
 *   readonly artistID: string
 * }
 * export type ExampleQuery$data = {
 *   readonly artist?: {
 *     readonly name?: string
 *   }
 * }
 * export type ExampleQuery = {
 *   readonly variables: ExampleQuery$variables
 *   readonly response: ExampleQuery$data
 * }
 */

// data is of type ExampleQuery$data
const data = useLazyLoadQuery(
  graphql`
    query ExampleQuery($artistID: ID!) {
      artist(id: $artistID) {
        name
      }
    }
  `,
  {artistID: 'banksy'},
);

return props.artist && <div>{props.artist.name} is great!</div>

Similarly, in this example the emitted type-information describes the type of the prop to match the type of the fragment reference useFragment expects to receive.

<Tabs
defaultValue={fbContent({internal: 'Flow', external: 'TypeScript'})}
values={[
{label: 'Flow', value: 'Flow'},
{label: 'TypeScript', value: 'TypeScript'},
]}>

javascript
/**
 * export type ExampleFragmentComponent_artist$data = {
 *   +name: string
 * }
 *
 * export type ExampleFragmentComponent_artist$key = { ... }
 */

import type { ExampleFragmentComponent_artist$key } from "__generated__/ExampleFragmentComponent_artist.graphql"

type Props = {
  artist: ExampleFragmentComponent_artist$key,
};

export default ExampleFragmentComponent(props) {
  // data is of type ExampleFragmentComponent_artist$data
  const data = useFragment(
    graphql`
      fragment ExampleFragmentComponent_artist on Artist {
        biography
      }
    `,
    props.artist,
  );

  return <div>About the artist: {props.artist.biography}</div>;
}
javascript
/**
 * export type ExampleFragmentComponent_artist$data = {
 *   readonly name: string
 * }
 *
 * export type ExampleFragmentComponent_artist$key = { ... }
 */

import { ExampleFragmentComponent_artist$key } from "__generated__/ExampleFragmentComponent_artist.graphql"

interface Props {
  artist: ExampleFragmentComponent_artist$key,
};

export default ExampleFragmentComponent(props: Props) {
  // data is of type ExampleFragmentComponent_artist$data
  const data = useFragment(
    graphql`
      fragment ExampleFragmentComponent_artist on Artist {
        biography
      }
    `,
    props.artist,
  );

  return <div>About the artist: {props.artist.biography}</div>;
}

Fragment references

The opaque identifier described in data-masking that a child container expects to receive from its parent, which represents the child container’s fragment spread inside the parent’s fragment.

:::important
Please read this important caveat about actually enabling type-safe fragment reference checking.
:::

Consider a component that composes the above fragment component example. In this example, the emitted type-information of the child component receives a unique opaque identifier type, called a fragment reference, which the type-information emitted for the parent’s fragment references in the location where the child’s fragment is spread. Thus ensuring that the child’s fragment is spread into the parent’s fragment and the correct fragment reference is passed to the child component at runtime.

<Tabs
defaultValue={fbContent({internal: 'Flow', external: 'TypeScript'})}
values={[
{label: 'Flow', value: 'Flow'},
{label: 'TypeScript', value: 'TypeScript'},
]}>

javascript
import { ExampleFragmentComponent } from "./ExampleFragmentComponent"

/**
 * import type { ExampleFragmentComponent_artist$fragmentType } from "ExampleFragmentComponent_artist.graphql";
 *
 * export type ExampleQuery$data = {
 *   +artist: ?{
 *     +name: ?string,
 *     +$fragmentSpreads: ExampleFragmentComponent_artist$fragmentType,
 *   }
 * };
 * export type ExampleQuery$variables = {
 *   +artistID: string,
 * }
 * export type ExampleQuery = {
 *   +variables: ExampleQuery$variables,
 *   +response: ExampleQuery$data,
 * }
 */

// data is of type ExampleQuery$data
const data = useLazyLoadQuery(
  graphql`
    query ExampleQuery($artistID: ID!) {
      artist(id: $artistID) {
        name
        ...ExampleFragmentComponent_artist
      }
    }
  `,
  {artistID: 'banksy'},
);

// Here only `data.artist.name` is directly visible,
// the marker prop $fragmentSpreads indicates that `data.artist`
// can be used for the component expecting this fragment spread.
return <ExampleFragmentComponent artist={data.artist} />;
javascript
import { ExampleFragmentComponent } from "./ExampleFragmentComponent"

/**
 * import { ExampleFragmentComponent_artist$fragmentType } from "ExampleFragmentComponent_artist.graphql";
 *
 * export type ExampleQuery$data = {
 *   readonly artist?: {
 *     readonly name: ?string,
 *     readonly " $fragmentSpreads": ExampleFragmentComponent_artist$fragmentType
 *   }
 * }
 * export type ExampleQuery$variables = {
 *   readonly artistID: string
 * }
 * export type ExampleQuery = {
 *   readonly variables: ExampleQuery$variables
 *   readonly response: ExampleQuery$data
 * }
 */

// data is of type ExampleQuery$data
const data = useLazyLoadQuery(
  graphql`
    query ExampleQuery($artistID: ID!) {
      artist(id: $artistID) {
        name
        ...ExampleFragmentComponent_artist
      }
    }
  `,
  {artistID: 'banksy'},
);

// Here only `data.artist.name` is directly visible,
// the marker prop $fragmentSpreads indicates that `data.artist`
// can be used for the component expecting this fragment spread.
return <ExampleFragmentComponent artist={data.artist} />;

Single artifact directory

An important caveat to note is that by default strict fragment reference type-information will not be emitted, instead they will be typed as any and would allow you to pass in any data to the child component.

To enable this feature, you will have to tell the compiler to store all the artifacts in a single directory, by specifying the artifactDirectory in the
compiler configuration:

text
{
  // package.json
  "relay": {
    "artifactDirectory": "./src/__generated__",
    ...
  },
  ...
}

…and additionally inform the babel plugin in your .babelrc config where to look for the artifacts:

json
{
  "plugins": [
    ["relay", { "artifactDirectory": "./src/__generated__" }]
  ]
}

It is recommended to alias this directory in your module resolution configuration such that you don’t need to specify relative paths in your source files. This is what is also done in the above examples, where artifacts are imported from a __generated__ alias, rather than relative paths like ../../../../__generated__.

Background information

The reason is that relay-compiler and its artifact emission is stateless. Meaning that it does not keep track of locations of original source files and where the compiler previously saved the accompanying artifact on disk. Thus, if the compiler were to emit artifacts that try to import fragment reference types from other artifacts, the compiler would:

  • first need to know where on disk that other artifact exists;
  • and update imports when the other artifact changes location on disk.

Facebook uses a module system called Haste, in which all source files are considered in a flat namespace. This means that an import declaration does not need to specify the path to another module and thus there is no need for the compiler to ever consider the above issues. I.e. an import only needs to specify the basename of the module filename and Haste takes care of actually finding the right module at import time. Outside of Facebook, however, usage of the Haste module system is non-existent nor encouraged, thus the decision to not import fragment reference types but instead type them as any.

At its simplest, we can consider Haste as a single directory that contains all module files, thus all module imports always being safe to import using relative sibling paths. This is what is achieved by the single artifact directory feature. Rather than co-locating artifacts with their source files, all artifacts are stored in a single directory, allowing the compiler to emit imports of fragment reference types.


Principles And Architecture Compiler Architecture (`website/docs/principles-and-architecture/compiler-architecture.mdx`)


id: compiler-architecture
title: Compiler Architecture
slug: /principles-and-architecture/compiler-architecture/
keywords:

  • compiler
  • architecture
  • transform

import DocsRating from '@site/src/core/DocsRating';

The compiler is a set of modules designed to extract GraphQL documents from across a codebase, transform/optimize them, and generate build artifacts. Examples of common types of artifacts include optimized GraphQL to persist to your server, runtime representations of the queries for use with GraphQL clients such as the Relay runtime, or generated source code for use with GraphQL frameworks for compiled languages (Java/Swift/etc).

Data Flow

The high-level flow of data through the compiler is represented in the following diagram:

text
┌─────────────┐┌─────────────┐
                   │   GraphQL   ││   Schema    │
                   └─────────────┘└─────────────┘
                          │              │              parse
                          └───────┬──────┘
                                  ▼
                   ┌────────────────────────────┐
                   │      CompilerContext       │
                   │                            │
                   │   ┌─────┐ ┌─────┐ ┌─────┐  │──┐
                   │   │ IR  │ │ IR  │ │ ... │  │  │
                   │   └─────┘ └─────┘ └─────┘  │  │
                   └────────────────────────────┘  │  transform/
                          │    │      ▲            │   optimize
                          │    │      └────────────┘
                          │    │
                          │    └──────────┐
                          │  print        │  codegen
                          ▼               ▼
                   ┌─────────────┐ ┌─────────────┐
                   │   GraphQL   │ │  Artifacts  │
                   └─────────────┘ └─────────────┘
  1. GraphQL text is extracted from source files and "parsed" into an intermediate representation (IR) using information from the schema.
  2. The set of IR documents forms a CompilerContext, which is then transformed and optimized.
  3. Finally, GraphQL is printed (e.g. to files, saved to a database, etc) and any artifacts are generated.

Data Types & Modules

The compiler module is composed of a set of core building blocks as well as a helper that packages them together in an easy to use API. Some of the main data types and modules in the compiler are as follows:

  • IR (Intermediate Representation): an (effectively immutable) representation of a GraphQL document (query, fragment, field, etc) as a tree structure, including type information from a schema. Compared to the standard GraphQL AST (produced by e.g. graphql-js) the main difference is that it encodes more of the semantics of GraphQL. For example, conditional branches (@include and @skip) are represented directly, making it easier to target optimizations for these directives (One such optimization is to merge sibling fields with the same condition, potentially reducing the number of conditionals that must be evaluated at runtime).
  • CompilerContext: an immutable representation of a corpus of GraphQL documents. It contains the schema and a mapping of document names to document representations (as IR, see above).
  • Transform: a "map"-like function that accepts a CompilerContext as input and returns a new, modified context as output. Examples below.
  • Parser: Converts a GraphQL schema and raw GraphQL text into typed IR objects.
  • Printer: a function that accepts IR and converts it to a GraphQL string.

The RelayCompiler module is a helper class that demonstrates one way of combining these primitives. It takes IR transforms, and given IR definitions, constructs a CompilerContext from them, transforming them, and generating output artifacts intended for use with Relay runtime.

Transforms

One of the main goals of the compiler is to provide a consistent platform for writing tools that transform or optimize GraphQL. This includes the ability to experiment with new directives by transforming them away at compile time. Transform functions should typically perform a single type of modification - it's expected that an app will have multiple transforms configured in the compiler instance.

Here are a few examples of some of the included transforms:

  • FlattenTransform: Reduces extraneous levels of indirection in a query, inlining fields from anonymous fragments wherever they match the parent type. This can be beneficial when generating code to read the results of a query or process query results, as it reduces duplicate field processing. For example:
text
# before: `id` is processed twice
foo { # type FooType
   id
   ... on FooType { # matches the parent type, so this is extraneous
     id
   }
 }

 # after: `id` is processed once
 foo {
   id
 }
  • SkipRedundantNodeTransform: A more advanced version of flattening, this eliminates more complex cases of field duplication such as when a field is fetched both unconditionally and conditionally, or is fetched by two different sub-fragments. For example:
text
# before: `id` processed up to 2x
foo {
  bar {
    id
  }
  ... on FooType @include(if: $cond) { # can't be flattened due to conditional
    id # but this field is guaranteed to be fetched regardless
  }
}

# after: `id` processed at most once
foo {
  bar {
    id
  }
}

Principles And Architecture Runtime Architecture (`website/docs/principles-and-architecture/runtime-architecture.mdx`)


id: runtime-architecture
title: Runtime Architecture
slug: /principles-and-architecture/runtime-architecture/
keywords:

  • runtime
  • architecture
  • store
  • DataID
  • Record
  • RecordSource

import DocsRating from '@site/src/core/DocsRating';

The Relay runtime is a full-featured GraphQL client that is designed for high performance even on low-end mobile devices and is capable of scaling to large, complex apps. The runtime API is not intended to be used directly in product code, but rather to provide a foundation for building higher-level product APIs such as React/Relay. This foundation includes:

  • A normalized, in-memory object graph/cache.
  • An optimized "write" operation for updating the cache with the results of queries/mutations/subscriptions.
  • A mechanism for reading data from the cache and subscribing for updates when these results change due to a mutation, subscription update, etc.
  • Garbage collection to evict entries from the cache when they can no longer be referenced by any view.
  • A generic mechanism for intercepting data prior to publishing it to the cache and either synthesizing new data or merging new and existing data together (which among other things enables the creation of a variety of pagination schemes).
  • Mutations with optimistic updates and the ability to update the cache with arbitrary logic.
  • Support for live queries where supported by the network/server.
  • Core primitives to enable subscriptions.
  • Core primitives for building offline/persisted caching.

Data Types

  • DataID (type): A globally unique or client-generated identifier for a record, stored as a string.
  • Record (type): A representation of a distinct data entity with an identity, type, and fields. Note that the actual runtime representation is opaque to the system: all accesses to Record objects (including record creation) is mediated through the RelayModernRecord module. This allows the representation itself to be changed in a single place (e.g. to use Maps or a custom class). It is important that other code does not assume that Records will always be plain objects.
  • RecordSource (type): A collection of records keyed by their data ID, used both to represent the cache and updates to it. For example the store's record cache is a RecordSource and the results of queries/mutations/subscriptions are normalized into RecordSources that are published to a store. Sources also define methods for asynchronously loading records in order to (eventually) support offline use-cases. Currently the only implementation of this interface is RelayInMemoryRecordSource; future implementations may add support for loading records from disk.
  • Store (type): The source of truth for an instance of RelayRuntime, holding the canonical set of records in the form of a RecordSource (though this is not required). Currently the only implementation is RelayModernStore.
  • Network (type): Provides methods for fetching query data from and executing mutations against an external data source.
  • Environment (type): Represents an encapsulated environment combining a Store and Network, providing a high-level API for interacting with both. This is the main public API of RelayRuntime.

Types for working with queries and their results include:

  • Selector (type): A selector defines the starting point for a traversal into the graph for the purposes of targeting a subgraph, combining a GraphQL fragment, variables, and the Data ID for the root object from which traversal should progress. Intuitively, this "selects" a portion of the object graph.
  • Snapshot (type): The (immutable) results of executing a Selector at a given point in time. This includes the selector itself, the results of executing it, and a list of the Data IDs from which data was retrieved (useful in determining when these results might change).

Data Model

Relay Runtime is designed for use with GraphQL schemas that describe object graphs in which objects have a type, an identity, and a set of fields with values. Objects may reference each other, which is represented by fields whose values are one or more other objects in the graph [1]. To distinguish from JavaScript Objects, these units of data are referred to as Records. Relay represents both its internal cache as well as query/mutation/etc results as a mapping of data IDs to records. The data ID is the unique (with respect to the cache) identifier for a record - it may be the value of an actual id field or based on the path to the record from the nearest object with an id (such path-based ids are called client ids). Each Record stores its data ID, type, and any fields that have been fetched. Multiple records are stored together as a RecordSource: a mapping of data IDs to Record instances.

For example, a user and their address might be represented as follows:

text
// GraphQL Fragment
fragment on User {
  id
  name
  address {
    city
  }
}

// Response
{
  id: '842472',
  name: 'Joe',
  address: {
    city: 'Seattle',
  }
}

// Normalized Representation
RecordSource {
  '842472': Record {
    __id: '842472',
    __typename: 'User', // the type is known statically from the fragment
    id: '842472',
    name: 'Joe',
    address: {__ref: 'client:842472:address'}, // link to another record
  },
  'client:842472:address': Record {
    // A client ID, derived from the path from parent & parent's ID
    __id: 'client:842472:address',
    __typename: 'Address',
    city: 'Seattle',
  }
}

[1] Note that GraphQL itself does not impose this constraint, and Relay Runtime may also be used for schemas that do not conform to it. For example, both systems can be used to query a single denormalized table. However, many of the features that Relay Runtime provides, such as caching and normalization, work best when the data is represented as a normalized graph with stable identities for discrete pieces of information.

Store Operations

The Store is the source of truth for application data and provides the following core operations.

  • lookup(selector: Selector): Snapshot: Reads the results of a selector from the store, returning the value given the data currently in the store.

  • subscribe(snapshot: Snapshot, callback: (snapshot: Snapshot) => void): Disposable: Subscribe to changes to the results of a selector. The callback is called when data has been published to the store that would cause the results of the snapshot's selector to change.

  • publish(source: RecordSource): void: Update the store with new information. All updates to the store are expressed in this form, including the results of queries/mutation/subscriptions as well as optimistic mutation updates. All of those operations internally create a new RecordSource instance and ultimately publish it to the store. Note that publish() does not immediately update any subscribe()-ers. Internally, the store compares the new RecordSource with its internal source, updating it as necessary:

    • Records that exist only in the published source are added to the store.
    • Records that exist in both are merged into a new record (inputs unchanged), with the result added to the store.
    • Records that are null in the published source are deleted (set to null) in the store.
    • Records with a special sentinel value are removed from the store. This supports un-publishing optimistically created records.
  • notify(): void: Calls any subscribe()-ers whose results have changed due to intervening publish()-es. Separating publish() and notify() allows for multiple payloads to be published before performing any downstream update logic (such as rendering).

  • retain(selector: Selector): Disposable: Ensure that all the records necessary to fulfill the given selector are retained in-memory. The records will not be eligible for garbage collection until the returned reference is disposed.

Example Data Flow: Fetching Query Data

text
┌───────────────────────┐
               │         Query         │
               └───────────────────────┘
                           │
                           ▼
                                             ┌ ─ ─ ─ ┐
                         fetch ◀────────────▶ Server
                                             └ ─ ─ ─ ┘
                           │
                     ┌─────┴───────┐
                     ▼             ▼
               ┌──────────┐  ┌──────────┐
               │  Query   │  │ Response │
               └──────────┘  └──────────┘
                     │             │
                     └─────┬───────┘
                           │
                           ▼
                       normalize
                           │
                           ▼
               ┌───────────────────────┐
               │     RecordSource      │
               │                       │
               │┌──────┐┌──────┐┌─────┐│
               ││Record││Record││ ... ││
               │└──────┘└──────┘└─────┘│
               └───────────────────────┘
  1. The query is fetched from the network.
  2. The query and response are traversed together, extracting the results into Record objects which are added to a fresh RecordSource.

This fresh RecordSource would then be published to the store:

text
publish
                           │
                           ▼
             ┌───────────────────────────┐
             │           Store           │
             │ ┌───────────────────────┐ │
             │ │     RecordSource      │ │
             │ │                       │ │
             │ │┌──────┐┌──────┐┌─────┐│ │
             │ ││Record││Record││ ... ││ │ <--- records are updated
             │ │└──────┘└──────┘└─────┘│ │
             │ └───────────────────────┘ │
             │ ┌───────────────────────┐ │
             │ │     Subscriptions     │ │
             │ │                       │ │
             │ │┌──────┐┌──────┐┌─────┐│ │
             │ ││ Sub. ││ Sub. ││ ... ││ │ <--- subscriptions do not fire yet
             │ │└──────┘└──────┘└─────┘│ │
             │ └───────────────────────┘ │
             └───────────────────────────┘

Publishing the results updates the store but does not immediately notify any subscribers. This is accomplished by calling notify()...

text
notify
                           │
                           ▼
             ┌───────────────────────────┐
             │           Store           │
             │ ┌───────────────────────┐ │
             │ │     RecordSource      │ │
             │ │                       │ │
             │ │┌──────┐┌──────┐┌─────┐│ │
             │ ││Record││Record││ ... ││ │
             │ │└──────┘└──────┘└─────┘│ │
             │ └───────────────────────┘ │
             │ ┌───────────────────────┐ │
             │ │     Subscriptions     │ │
             │ │                       │ │
             │ │┌──────┐┌──────┐┌─────┐│ │
             │ ││  Sub.││  Sub.││  ...││ │ <--- affected subscriptions fire
             │ │└──────┘└──────┘└─────┘│ │
             │ └───┼───────┼───────┼───┘ │
             └─────┼───────┼───────┼─────┘
                   │       │       │
                   ▼       │       │
               callback    │       │
                           ▼       │
                       callback    │
                                   ▼
                               callback

...which calls the callbacks for any subscribe()-ers whose results have changed. Each subscription is checked as follows:

  1. First, the list of data IDs that have changed since the last notify() is compared against data IDs listed in the subscription's latest Snapshot. If there is no overlap, the subscription's results cannot possibly have changed (if you imagine the graph visually, there is no overlap between the part of the graph that changed and the part that is selected). In this case the subscription is ignored, otherwise processing continues.
  2. Second, any subscriptions that do have overlapping data IDs are re-read, and the new/previous results are compared. If the result has not changed, the subscription is ignored (this can occur if a field of a record changed that is not relevant to the subscription's selector), otherwise processing continues.
  3. Finally, subscriptions whose data actually changed are notified via their callback.

Example Data Flow: Reading and Observing the Store

Products access the store primarily via lookup() and subscribe(). Lookup reads the initial results of a fragment, and subscribe observes that result for any changes. Note that the output of lookup() - a Snapshot - is the input to subscribe(). This is important because the snapshot contains important information that can be used to optimize the subscription - if subscribe() accepted only a Selector, it would have to re-read the results in order to know what to subscribe to, which is less efficient.

Therefore a typical data flow is as follows - note that this flow is managed automatically by higher-level APIs such as React/Relay. First a component will lookup the results of a selector against a record source (e.g. the store's canonical source):

text
┌───────────────────────┐       ┌──────────────┐
    │     RecordSource      │       │              │
    │                       │       │              │
    │┌──────┐┌──────┐┌─────┐│       │   Selector   │
    ││Record││Record││ ... ││       │              │
    │└──────┘└──────┘└─────┘│       │              │
    └───────────────────────┘       └──────────────┘
                │                           │
                │                           │
                └──────────────┬────────────┘
                               │
                               │  lookup
                               │  (read)
                               │
                               ▼
                        ┌─────────────┐
                        │             │
                        │  Snapshot   │
                        │             │
                        └─────────────┘
                               │
                               │  render, etc
                               │
                               ▼

Next, it will subscribe() using this snapshot in order to be notified of any changes - see the above diagram for publish() and notify().


Principles And Architecture Thinking In Graphql (`website/docs/principles-and-architecture/thinking-in-graphql.mdx`)


id: thinking-in-graphql
title: Thinking in GraphQL
slug: /principles-and-architecture/thinking-in-graphql/
keywords:

  • GraphQL

import DocsRating from '@site/src/core/DocsRating';

GraphQL presents new ways for clients to fetch data by focusing on the needs of product developers and client applications. It provides a way for developers to specify the precise data needed for a view and enables a client to fetch that data in a single network request. Compared to traditional approaches such as REST, GraphQL helps applications to fetch data more efficiently (compared to resource-oriented REST approaches) and avoid duplication of server logic (which can occur with custom endpoints). Furthermore, GraphQL helps developers to decouple product code and server logic. For example, a product can fetch more or less information without requiring a change to every relevant server endpoint. It's a great way to fetch data.

In this article we'll explore what it means to build a GraphQL client framework and how this compares to clients for more traditional REST systems. Along the way we'll look at the design decisions behind Relay and see that it's not just a GraphQL client but also a framework for declarative data-fetching. Let's start at the beginning and fetch some data!

Fetching Data

Imagine we have a simple application that fetches a list of stories, and some details about each one. Here's how that might look in resource-oriented REST:

javascript
// Fetch the list of story IDs but not their details:
rest.get('/stories').then(stories =>
  // This resolves to a list of items with linked resources:
  // `[ { href: "http://.../story/1" }, ... ]`
  Promise.all(stories.map(story =>
    rest.get(story.href) // Follow the links
  ))
).then(stories => {
  // This resolves to a list of story items:
  // `[ { id: "...", text: "..." } ]`
  console.log(stories);
});

Note that this approach requires n+1 requests to the server: 1 to fetch the list, and n to fetch each item. With GraphQL we can fetch the same data in a single network request to the server (without creating a custom endpoint that we'd then have to maintain):

javascript
graphql.get(`query { stories { id, text } }`).then(
  stories => {
    // A list of story items:
    // `[ { id: "...", text: "..." } ]`
    console.log(stories);
  }
);

So far we're just using GraphQL as a more efficient version of typical REST approaches. Note two important benefits in the GraphQL version:

  • All data is fetched in a single round trip.
  • The client and server are decoupled: the client specifies the data needed instead of relying on the server endpoint to return the correct data.

For a simple application that's already a nice improvement.

Client Caching

Repeatedly refetching information from the server can get quite slow. For example, navigating from the list of stories, to a list item, and back to the list of stories means we have to refetch the whole list. We'll solve this with the standard solution: caching.

In a resource-oriented REST system, we can maintain a response cache based on URIs:

javascript
var _cache = new Map();
rest.get = uri => {
  if (!_cache.has(uri)) {
    _cache.set(uri, fetch(uri));
  }
  return _cache.get(uri);
};

Response-caching can also be applied to GraphQL. A basic approach would work similarly to the REST version. The text of the query itself can be used as a cache key:

javascript
var _cache = new Map();
graphql.get = queryText => {
  if (!_cache.has(queryText)) {
    _cache.set(queryText, fetchGraphQL(queryText));
  }
  return _cache.get(queryText);
};

Now, requests for previously cached data can be answered immediately without making a network request. This is a practical approach to improving the perceived performance of an application. However, this method of caching can cause problems with data consistency.

Cache Consistency

With GraphQL it is very common for the results of multiple queries to overlap. However, our response cache from the previous section doesn't account for this overlap — it caches based on distinct queries. For example, if we issue a query to fetch stories:

graphql
query { stories { id, text, likeCount } }

and then later refetch one of the stories whose likeCount has since been incremented:

graphql
query { story(id: "123") { id, text, likeCount } }

We'll now see different likeCounts depending on how the story is accessed. A view that uses the first query will see an outdated count, while a view using the second query will see the updated count.

Caching A Graph

The solution to caching GraphQL is to normalize the hierarchical response into a flat collection of records. Relay implements this cache as a map from IDs to records. Each record is a map from field names to field values. Records may also link to other records (allowing it to describe a cyclic graph), and these links are stored as a special value type that references back into the top-level map. With this approach each server record is stored once regardless of how it is fetched.

Here's an example query that fetches a story's text and its author's name:

graphql
query {
  story(id: "1") {
    text,
    author {
      name
    }
  }
}

And here's a possible response:

json
{
  "query": {
    "story": {
      "text": "Relay is open-source!",
      "author": {
        "name": "Jan"
      }
    }
  }
}

Although the response is hierarchical, we'll cache it by flattening all the records. Here is an example of how Relay would cache this query response:

javascript
Map {
  // `story(id: "1")`
  1: Map {
    text: 'Relay is open-source!',
    author: Link(2),
  },
  // `story.author`
  2: Map {
    name: 'Jan',
  },
};

This is only a simple example: in reality the cache must handle one-to-many associations and pagination (among other things).

Using The Cache

So how do we use this cache? Let's look at two operations: writing to the cache when a response is received, and reading from the cache to determine if a query can be fulfilled locally (the equivalent to _cache.has(key) above, but for a graph).

Populating The Cache

Populating the cache involves walking a hierarchical GraphQL response and creating or updating normalized cache records. At first it may seem that the response alone is sufficient to process the response, but in fact this is only true for very simple queries. Consider user(id: "456") { photo(size: 32) { uri } } — how should we store photo? Using photo as the field name in the cache won't work because a different query might fetch the same field but with different argument values (e.g. photo(size: 64) {...}). A similar issue occurs with pagination. If we fetch the 11th to 20th stories with stories(first: 10, offset: 10), these new results should be appended to the existing list.

Therefore, a normalized response cache for GraphQL requires processing payloads and queries in parallel. For example, the photo field from above might be cached with a generated field name such as photo_size(32) in order to uniquely identify the field and its argument values.

Reading From Cache

To read from the cache we can walk a query and resolve each field. But wait: that sounds exactly like what a GraphQL server does when it processes a query. And it is! Reading from the cache is a special case of an executor where a) there's no need for user-defined field functions because all results come from a fixed data structure and b) results are always synchronous — we either have the data cached or we don't.

Relay implements several variations of query traversal: operations that walk a query alongside some other data such as the cache or a response payload. For example, when a query is fetched Relay performs a "diff" traversal to determine what fields are missing (much like React diffs virtual DOM trees). This can reduce the amount of data fetched in many common cases and even allow Relay to avoid network requests at all when queries are fully cached.

Cache Updates

Note that this normalized cache structure allows overlapping results to be cached without duplication. Each record is stored once regardless of how it is fetched. Let's return to the earlier example of inconsistent data and see how this cache helps in that scenario.

The first query was for a list of stories:

graphql
query { stories { id, text, likeCount } }

With a normalized response cache, a record would be created for each story in the list. The stories field would store links to each of these records.

The second query refetched the information for one of those stories:

graphql
query { story(id: "123") { id, text, likeCount } }

When this response is normalized, Relay can detect that this result overlaps with existing data based on its id. Rather than create a new record, Relay will update the existing 123 record. The new likeCount is therefore available to both queries, as well as any other query that might reference this story.

Data/View Consistency

A normalized cache ensures that the cache is consistent. But what about our views? Ideally, our React views would always reflect the current information from the cache.

Consider rendering the text and comments of a story along with the corresponding author names and photos. Here's the GraphQL query:

graphql
query {
  story(id: "1") {
    text,
    author { name, photo },
    comments {
      text,
      author { name, photo }
    }
  }
}

After initially fetching this story our cache might be as follows. Note that the story and comment both link to the same record as author:

text
// Note: This is pseudo-code for `Map` initialization to make the structure
// more obvious.
Map {
  // `story(id: "1")`
  1: Map {
    text: 'got GraphQL?',
    author: Link(2),
    comments: [Link(3)],
  },
  // `story.author`
  2: Map {
    name: 'Yuzhi',
    photo: 'http://.../photo1.jpg',
  },
  // `story.comments[0]`
  3: Map {
    text: 'Here\'s how to get one!',
    author: Link(2),
  },
}

The author of this story also commented on it — quite common. Now imagine that some other view fetches new information about the author, and her profile photo has changed to a new URI. Here's the only part of our cached data that changes:

text
Map {
  ...
  2: Map {
    ...
    photo: 'http://.../photo2.jpg',
  },
}

The value of the photo field has changed; and therefore the record 2 has also changed. And that's it. Nothing else in the cache is affected. But clearly our view needs to reflect the update: both instances of the author in the UI (as story author and comment author) need to show the new photo.

A standard response is to "just use immutable data structures" — but let's see what would happen if we did:

text
ImmutableMap {
  1: ImmutableMap // same as before
  2: ImmutableMap {
    ... // other fields unchanged
    photo: 'http://.../photo2.jpg',
  },
  3: ImmutableMap // same as before
}

If we replace 2 with a new immutable record, we'll also get a new immutable instance of the cache object. However, records 1 and 3 are untouched. Because the data is normalized, we can't tell that story's contents have changed just by looking at the story record alone.

Achieving View Consistency

There are a variety of solutions for keeping views up to date with a flattened cache. The approach that Relay takes is to maintain a mapping from each UI view to the set of IDs it references. In this case, the story view would subscribe to updates on the story (1), the author (2), and the comments (3 and any others). When writing data into the cache, Relay tracks which IDs are affected and notifies only the views that are subscribed to those IDs. The affected views re-render, and unaffected views opt-out of re-rendering for better performance (Relay provides a safe but effective default shouldComponentUpdate). Without this strategy, every view would re-render for even the tiniest change.

Note that this solution will also work for writes: any update to the cache will notify the affected views, and writes are just another thing that updates the cache.

Mutations

So far we've looked at the process of querying data and keeping views up to date, but we haven't looked at writes. In GraphQL, writes are called mutations. We can think of them as queries with side effects. Here's an example of calling a mutation that might mark a given story as being liked by the current user:

graphql
// Give a human-readable name and define the types of the inputs,
// in this case the id of the story to mark as liked.
mutation StoryLike($storyID: String) {
   // Call the mutation field and trigger its side effects
   storyLike(storyID: $storyID) {
     // Define fields to re-fetch after the mutation completes
     likeCount
   }
}

Notice that we're querying for data that may have changed as a result of the mutation. An obvious question is: why can't the server just tell us what changed? The answer is: it's complicated. GraphQL abstracts over any data storage layer (or an aggregation of multiple sources), and works with any programming language. Furthermore, the goal of GraphQL is to provide data in a form that is useful to product developers building a view.

We've found that it's common for the GraphQL schema to differ slightly or even substantially from the form in which data is stored on disk. Put simply: there isn't always a 1:1 correspondence between data changes in your underlying data storage (disk) and data changes in your product-visible schema (GraphQL). The perfect example of this is privacy: returning a user-facing field such as age might require accessing numerous records in our data-storage layer to determine if the active user is even allowed to see that age (Are we friends? Is my age shared? Did I block you? etc.).

Given these real-world constraints, the approach in GraphQL is for clients to query for things that may change after a mutation. But what exactly do we put in that query? During the development of Relay we explored several ideas — let's look at them briefly in order to understand why Relay uses the approach that it does:

  • Option 1: Re-fetch everything that the app has ever queried. Even though only a small subset of this data will actually change, we'll still have to wait for the server to execute the entire query, wait to download the results, and wait to process them again. This is very inefficient.

  • Option 2: Re-fetch only the queries required by actively rendered views. This is a slight improvement over option 1. However, cached data that isn't currently being viewed won't be updated. Unless this data is somehow marked as stale or evicted from the cache subsequent queries will read outdated information.

  • Option 3: Re-fetch a fixed list of fields that may change after the mutation. We'll call this list a fat query. We found this to also be inefficient because typical applications only render a subset of the fat query, but this approach would require fetching all of those fields.

  • Option 4 (Relay): Re-fetch the intersection of what may change (the fat query) and the data in the cache. In addition to the cache of data Relay also remembers the queries used to fetch each item. These are called tracked queries. By intersecting the tracked and fat queries, Relay can query exactly the set of information the application needs to update and nothing more.

Data-Fetching APIs

So far we looked at the lower-level aspects of data-fetching and saw how various familiar concepts translate to GraphQL. Next, let's step back and look at some higher-level concerns that product developers often face around data-fetching:

  • Fetching all the data for a view hierarchy.
  • Managing asynchronous state transitions and coordinating concurrent requests.
  • Managing errors.
  • Retrying failed requests.
  • Updating the local cache after receiving query/mutation responses.
  • Queuing mutations to avoid race conditions.
  • Optimistically updating the UI while waiting for the server to respond to mutations.

We've found that typical approaches to data-fetching — with imperative APIs — force developers to deal with too much of this non-essential complexity. For example, consider optimistic UI updates. This is a way of giving the user feedback while waiting for a server response. The logic of what to do can be quite clear: when the user clicks "like", mark the story as being liked and send the request to the server. But the implementation is often much more complex. Imperative approaches require us to implement all of those steps: reach into the UI and toggle the button, initiate a network request, retry it if necessary, show an error if it fails (and untoggle the button), etc. The same goes for data-fetching: specifying what data we need often dictates how and when it is fetched. Next, we'll explore our approach to solving these concerns with Relay.


Principles And Architecture Thinking In Relay (`website/docs/principles-and-architecture/thinking-in-relay.mdx`)


id: thinking-in-relay title: Thinking in Relay slug: /principles-and-architecture/thinking-in-relay/

import DocsRating from '@site/src/core/DocsRating';

Relay's approach to data-fetching is heavily inspired by our experience with React. In particular, React breaks complex interfaces into reusable components, allowing developers to reason about discrete units of an application in isolation, and reducing the coupling between disparate parts of an application. Even more important is that these components are declarative: they allow developers to specify what the UI should look like for a given state, and not have to worry about how to show that UI. Unlike previous approaches that used imperative commands to manipulate native views (e.g. the DOM), React uses a UI description to automatically determine the necessary commands.

Let's look at some product use-cases to understand how we incorporated these ideas into Relay. We'll assume a basic familiarity with React.

Fetching Data For a View

In our experience, the overwhelming majority of products want one specific behavior: fetch all the data for a view hierarchy while displaying a loading indicator, and then render the entire view once the data is available.

One solution is to have a root component declare and fetch the data required by it and all of its children. However, this would introduce coupling: any change to a child component would require changing any root component that might render it! This coupling could mean a greater chance for bugs and slow the pace of development.

Another logical approach is to have each component declare and fetch the data it requires. This sounds great. However, the problem is that a component may render different children based on the data it received. So, nested components will be unable to render and begin fetching their data until parent components' queries have completed. In other words, this forces data fetching to proceed in stages: first render the root and fetch the data it needs, then render its children and fetch their data, and so on until you reach leaf components. Rendering would require multiple slow, serial roundtrips.

Relay combines the advantages of both of these approaches by allowing components to specify what data they require, but to coalesce those requirements into a single query that fetches the data for an entire subtree of components. In other words, it determines statically (i.e. before your application runs; at the time you write your code) the requirements for an entire view!

This is achieved with the help of GraphQL. Functional components use one or more GraphQL fragments to describe their data requirements. These fragments are then nested within other fragments, and ultimately within queries. And when such a query is fetched, Relay will make a single network request for it and all of its nested fragments. In other words, the Relay runtime is then able to make a single network request for all of the data required by a view!

Let's dive deeper to understand how Relay achieves this feat.

Specifying the data requirements of a component

With Relay, the data requirements for a component are specified with fragments. Fragments are named snippets of GraphQL that specify which fields to select from an object of a particular type. Fragments are written within GraphQL literals. For example, the following declares a GraphQL literal containing a fragment which selects an author's name and photo url:

javascript
// AuthorDetails.react.js
const authorDetailsFragment = graphql`
  fragment AuthorDetails_author on Author {
    name
    photo {
      url
    }
  }
`;

This data is then read out from the store by calling the useFragment(...) hook in a functional React component. The actual author from which to read this data is determined by the second parameter passed to useFragment. For example:

javascript
// AuthorDetails.react.js
export default function AuthorDetails(props) {
  const data = useFragment(authorDetailsFragment, props.author);
  // ...
}

This second parameter (props.author) is a fragment reference. Fragment references are obtained by spreading a fragment into another fragment or query. Fragments cannot be fetched directly. Instead, all fragments must ultimately be spread (either directly or transitively) into a query for the data to be fetched.

Let's take a look at one such query.

Queries

In order to fetch that data, we might declare a query which spreads AuthorDetails_author as follows:

javascript
// Story.react.js
const storyQuery = graphql`
  query StoryQuery($storyID: ID!) {
    story(id: $storyID) {
      title
      author {
        ...AuthorDetails_author
      }
    }
  }
`;

Now, we can fetch the query by calling const data = useLazyLoadQuery(storyQuery, {storyID}). At this point, data.story.author (if it is present; all fields are nullable by default) will be a fragment reference that we can pass to AuthorDetails. For example:

javascript
// Story.react.js
function Story(props) {
  const data = useLazyLoadQuery(storyQuery, props.storyId);

  return (<>
    <Heading>{data?.story.title}</Heading>
    {data?.story?.author && <AuthorDetails author={data.story.author} />}
  </>);
}

Note what has happened here. We made a single network request which contained the data required by both the Story component and the AuthorDetails component! When that data was available, the entire view could render in a single pass.

Data Masking

With typical approaches to data-fetching we found that it was common for two components to have implicit dependencies. For example <Story /> might use some data without directly ensuring that the data was fetched. This data would often be fetched by some other part of the system, such as <AuthorDetails />. Then when we changed <AuthorDetails /> and removed that data-fetching logic, <Story /> would suddenly and inexplicably break. These types of bugs are not always immediately apparent, especially in larger applications developed by larger teams. Manual and automated testing can only help so much: this is exactly the type of systematic problem that is better solved by a framework.

We've seen that Relay ensures that the data for a view is fetched all at once. But Relay also provide another benefit that isn't immediately obvious: data masking. Relay only allows components to access data they specifically ask for in GraphQL fragments, and nothing more. So if one component queries for a Story's title, and another for its text, each can see only the field that they asked for. In fact, components can't even see the data requested by their children: that would also break encapsulation.

Relay also goes further: it uses opaque identifiers on props to validate that we've explicitly fetched the data for a component before rendering it. If <Story /> renders <AuthorDetails /> but forgets to spread its fragment, Relay will warn that the data for <AuthorDetails /> is missing. In fact, Relay will warn even if some other component happened to fetch the same data required by <AuthorDetails />. This warning tells us that although things might work now, they're highly likely to break later.

Conclusion

GraphQL provides a powerful tool for building efficient, decoupled client applications. Relay builds on this functionality to provide a framework for declarative data-fetching. By separating what data to fetch from how it is fetched, Relay helps developers build applications that are robust, transparent, and performant by default. It's a great complement to the component-centric way of thinking championed by React. While each of these technologies — React, Relay, and GraphQL — are powerful on their own, the combination is a UI platform that allows us to move fast and ship high-quality apps at scale.


Principles And Architecture Videos (`website/docs/principles-and-architecture/videos.mdx`)


id: videos title: Videos slug: /principles-and-architecture/videos/

import DocsRating from '@site/src/core/DocsRating';

React Conf 2021

Re-introducing Relay | Robert Balicki

React Conf 2019

Data Fetching With Suspense In Relay | Joe Savona

Facebook F8 2019

[Building the new Facebook.com with React, GraphQL and Relay](https://developers.facebook.com/videos/2019/building-the-new-facebookcom-with-react-graphql-and-relay/)

Facebook F8 2017

[The Evolution of React and GraphQL at Facebook and Beyond](https://developers.facebook.com/videos/f8-2017/the-evolution-of-react-and-graphql-at-facebook-and-beyond/)

[Silicon Valley ReactJS Meetup](http://www.meetup.com/Silicon-Valley-ReactJS-Meetup/)

Relay Modern: simpler, faster, more predictable ([slides](https://speakerdeck.com/wincent/relay-2-simpler-faster-more-predictable))

Zero to GraphQL in 30 minutes

[GraphQL Europe 2018](https://www.graphql-europe.org/)

Data Masking in GraphQL Clients


Tutorial Arrays Lists (`website/docs/tutorial/arrays-lists.mdx`)

Arrays and Lists

So far we’ve only dealt with components that have a single instance of the components they’re composed from. For example, we’re only showing a single Newsfeed story, and within that story there’s just a single author with a single profile picture. Let’s look at how to handle more than one of something.

GraphQL includes support for arrays, which in GraphQL are called lists. A field can be not only a single scalar value but an array of them, or not only a single edge but an array of edges. The schema specifies whether each field is a list or not, but, oddly, the GraphQL query syntax doesn’t distinguish between selecting a singular field and selecting a list — a quirky exception to the design principle that a GraphQL response should have the same shape as the query.

Request:

text
query MyQuery {
  viewer {
    contacts { // List of edges
      id // field on a single item
      name
    }
  }
}

Response:

text
{
  viewer: {
    contacts: [ // array in response
      {
        id: "123",
        name: "Chris",
      },
      {
        id: "789",
        name: "Sue",
      }
    ]
  }
}

As it happens, the schema in our example app has a topStories field that returns a list of Stories, as opposed to the topStory field we're currently using that returns just one.

To show multiple stories on our newsfeed, we just need to modify Newsfeed.tsx to use topStories.

Step 1 — Select a list in the fragment

Open Newsfeed.tsx and find NewsfeedQuery. Replace topStory with topStories, and run npm run relay.

text
const NewsfeedQuery = graphql`
  query NewsfeedQuery {
    // change-line
    topStories {
      ...StoryFragment
    }
  }
`;

Step 2 — Map over the list in the component

In the Newsfeed component, data.topStories will now be an array of fragment refs, each of which can be passed to a Story child component to render that story:

text
export default function Newsfeed({}) {
  const data = useLazyLoadQuery<NewsfeedQueryType>(NewsfeedQuery, {});
  // change-line
  const stories = data.topStories;
  return (
    <div className="newsfeed">
      // change-line
      {stories.map(story => <Story story={story} />)}
    </div>
  );
}

Step 3 — Add a React key based on the node ID

At this point, you should see multiple stories on the screen! It's beginning to look like a proper newsfeed app.

However, we're also getting a React warning that we're rendering an array of components without providing a key attribute.

It's always important to heed this warning, and more specifically to base keys on the identity of the things being displayed, rather than simply their indices in the array. This allows React to handle reordering and deleting items from the list correctly, since it knows which items are which even if their order changes.

Luckily, GraphQL nodes generally have IDs. We can simply select the id field of story and use it as a key:

text
const NewsfeedQuery = graphql`
  query NewsfeedQuery {
    topStories {
      // change-line
      id
      ...StoryFragment
    }
  }
`;

...

export default function Newsfeed({}) {
  const data = useLazyLoadQuery<NewsfeedQueryType>(NewsfeedQuery, {});
  const stories = data.topStories;
  return (
    <div className="newsfeed">
      {stories.map(story => (
        <Story
          // change-line
          key={story.id}
          story={story}
        />
      ))}
    </div>
  );
}

With that, we've got a collection of Stories on the screen. It's worth pointing out that here we're mixing individual fields with fragment spreads in the same place in our query. This means that Newsfeed can read the fields it cares about (directly from useLazyLoadQuery) while Story can read the fields it cares about (via useFragment). The same object both contains Newsfeed's selected field id and is also a fragment key for StoryFragment.

:::tip
GraphQL Lists are only the most basic way of dealing with collections of things. We’ll build on them to do pagination and infinite scrolling later in the tutorial, using a special system called Connections. You’ll want to use Connections in most situations where you have a collection of items — although you’ll still use GraphQL Lists as a building block.
:::

Onward!


Tutorial Fragments 1 (`website/docs/tutorial/fragments-1.mdx`)

Fragments

Fragments are one of the distinguishing features of Relay. They let each component declare its own data needs independently, while retaining the efficiency of a single query. In this section, we’ll show how to split a query up into fragments.

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';


To start with, let’s say we want our Story component to show the date that the story was posted. To do that, we need some more data from the server, so we’re going to have to add a field to the query.

Go to Newsfeed.tsx and find NewsfeedQuery so that you can add the new field:

text
const NewsfeedQuery = graphql`
  query NewsfeedQuery {
    topStory {
      title
      summary
      // change-line
      createdAt // Add this line
      poster {
        name
        profilePicture {
          url
        }
      }
      thumbnail {
        url
      }
    }
  }
`;

Now we've updated the query, we need to run the Relay compiler so that it knows about the updated Graphql query by running npm run relay.

Next, go to Story.tsx and modify it to display the date:

text
// change-line
import Timestamp from './Timestamp';

type Props = {
  story: {
    // change-line
    createdAt: string; // Add this line
    ...
  };
};

export default function Story({story}: Props) {
  return (
    <Card>
      <PosterByline poster={story.poster} />
      <Heading>{story.title}</Heading>
      // change-line
      <Timestamp time={story.createdAt} /> // Add this line
      <Image image={story.thumbnail} width={400} height={400} />
      <StorySummary summary={story.summary} />
    </Card>
  );
}

The date should now appear. And thanks to GraphQL, we didn't have to write and deploy any new server code.

But if you think about it, why should you have had to modify Newsfeed.tsx? Shouldn’t React components be self-contained? Why should Newsfeed care about the specific data required by Story? What if the data was required by some child component of Story way down in the hierarchy? What if it was a component that was used in many different places? Then we would have to modify many components whenever its data requirements changed.

To avoid these and many other problems, we can move the data requirements for the Story component into Story.tsx.

We do this by splitting off Story’s data requirements into a fragment defined in Story.tsx. Fragments are separate pieces of GraphQL that the Relay compiler stitches together into complete queries. They allow each component to define its own data requirements, without paying the cost at runtime of each component running its own queries.

Let’s go ahead and split Story’s data requirements into a fragment now.


Step 1 — Define a fragment

Add the following to Story.tsx (within src/components) above the Story component:

text
import { graphql } from 'relay-runtime';

const StoryFragment = graphql`
  fragment StoryFragment on Story {
    title
    summary
    createdAt
    poster {
      name
      profilePicture {
        url
      }
    }
    thumbnail {
      url
    }
  }
`;

Note that we’ve taken all of the selections from within topStory in our query and copied them into this new Fragment declaration. Like queries, fragments have a name (StoryFragment), which we’ll use in a moment, but they also have a GraphQL type (Story) that they’re “on”. This means that this fragment can be used whenever we have a Story node in the graph.

Step 2 — Spread the fragment

Go to Newsfeed.tsx and modify NewsfeedQuery to look like this:

text
const NewsfeedQuery = graphql`
  query NewsfeedQuery {
    topStory {
      // change-line
      ...StoryFragment
    }
  }
`;

We’ve replaced the selections inside topStory with StoryFragment. The Relay compiler will make sure that all of Story’s data gets fetched from now on, without having to change Newsfeed.

Step 3 — Call useFragment

You’ll notice that Story now renders an empty card! All the data is missing! Wasn’t Relay supposed to include the fields selected by the fragment in the story object obtained from useLazyLoadQuery()?

The reason is that Relay hides them. Unless a component specifically asks for the data for a certain fragment, that data will not be visible to the component. This is called data masking, and enforces that components don’t implicitly rely on another component’s data dependencies, but declare all of their dependencies within their own fragments. This keeps components self-contained and maintainable.

Without data masking, you could never remove a field from a fragment, because it would be hard to verify that some other component somewhere wasn’t using it.

To access the data selected by a fragment, we use a hook called useFragment. Modify Story to look like this:

text
import { useFragment } from 'react-relay';

export default function Story({story}: Props) {
  const data = useFragment(
    // color1
    StoryFragment,
    // color2
    story,
  );
  return (
    <Card>
      <Heading>{data.title}</Heading>
      <PosterByline poster={data.poster} />
      <Timestamp time={data.createdAt} />
      <Image image={data.thumbnail} width={400} height={400} />
      <StorySummary summary={data.summary} />
    </Card>
  );
}

useFragment takes two arguments:

  • The GraphQL tagged string literal for the fragment we want to read
  • The same story object as we used before, which comes from the place within a GraphQL query where we spread the fragment. This is called a fragment key.

It returns the data selected by that fragment.

:::tip
We’ve rewritten story to data (the data returned by useFragment) in all of the JSX here; make sure to do the same in your copy of the component, or it won't work.
:::

Fragment keys are the places in a GraphQL query response where a fragment was spread. For example, given the Newsfeed query:

text
query NewsfeedQuery {
  topStory {
    ...StoryFragment
  }
}

Then if queryResult is the object returned by useLazyLoadQuery, queryResult.topStory will be a fragment key for StoryFragment.

Technically, queryResult.topStory is an object that contains some hidden fields that tell Relay's useFragment where to look for the data it needs. The fragment key specifies both which node to read from (here there's just one story, but soon we'll have multiple stories), and what fields can be read out (the fields selected by that specific fragment). The useFragment hook then reads that specific information out of Relay's local data store.

:::note
As we'll see in later examples, you can spread multiple fragments into the same place in a query, and also mix fragment spreads with directly-selected fields.
:::

Step 4 — TypeScript types for fragment refs

To complete the fragmentization, we also need to change the type definition for Props so that TypeScript knows this component expects to receive a fragment key instead of the raw data.

Recall that when you spread a fragment into a query (or another fragment), the part of the query result corresponding to where the fragment is spread becomes a fragment key for that fragment. This fragment key is the object that you pass to a component to tell it where in the graph to read the fragment from.

To make this type-safe, Relay generates a type that represents the fragment key for that specific fragment — this way, if you try to use a component without spreading its fragment into your query, you won’t be able to provide a fragment key that satisfies the type system. Here are the changes we need to make:

text
// change-line
import type {StoryFragment$key} from './__generated__/StoryFragment.graphql';

type Props = {
  // change-line
  story: StoryFragment$key;
};

With that done, we have a Newsfeed that no longer has to care what data Story requires, yet can still fetch that data up-front within its own query.


Exercise

The PosterByline component used by Story renders the poster’s name and profile picture. Use these same steps to fragmentize PosterByline. You need to:

  • Declare a PosterBylineFragment on Actor and specify the fields it needs (name, profilePicture). The Actor type represents a person or organization that can post a story.
  • Spread that fragment within poster in StoryFragment.
  • Call useFragment to retrieve the data.
  • Update the Props to accept a PosterBylineFragment$key as the poster prop.

It’s worth going through these steps a second time, to get the mechanics of using fragments under your fingers. There are a lot of parts here that need to slot together in the right way.

Once you’ve done that, let’s look at a basic example of how fragments help an app to scale.


Reusing a Fragment in Multiple Places

A fragment says, given some graph node of a particular type, what data to read from that node. The fragment key specifies which node in the graph the data is selected from. A re-usable component that specifies a fragment can retrieve the data from different parts of the graph in different contexts, by being passed a different fragment key.

For example, notice that the Image component is used in two places: directly within Story for the story’s thumbnail image, and also within PosterByline for the poster’s profile pic. Let’s fragmentize Image and see how it can select the data it needs from different places in the graph according to where it is used.

Step 1 — Define the fragment

Open up Image.tsx and add a Fragment definition:

text
import { graphql } from 'relay-runtime';

const ImageFragment = graphql`
  fragment ImageFragment on Image {
    url
  }
`;

Step 2 — Spread the fragment

Go back to StoryFragment and PosterBylineFragment and spread ImageFragment into it in each place where the Image component is what’s using the data:

text
const StoryFragment = graphql`
  fragment StoryFragment on Story {
    title
    summary
    postedAt
    poster {
      ...PosterBylineFragment
    }
    thumbnail {
      // change-line
      ...ImageFragment
    }
  }
`;
text
const PosterBylineFragment = graphql`
  fragment PosterBylineFragment on Actor {
    name
    profilePicture {
      // change-line
      ...ImageFragment
    }
  }
`;

Step 3 — Call useFragment

Modify the Image component to read the fields using its fragment, and also modify its Props to accept the fragment key:

text
import { useFragment } from 'react-relay';
import type { ImageFragment$key } from "./__generated__/ImageFragment.graphql";

type Props = {
  image: ImageFragment$key;
  ...
};

function Image({image}: Props) {
  const data = useFragment(ImageFragment, image);
  return <img key={data.url} src={data.url} ... />
}

Step 4 — Modify once, enjoy everywhere

Now that we’ve fragmentized Image’s data requirements and co-located them within the component, we can add new data dependencies to Image without modifying any of the components that use it.

For example, let’s add an altText label for accessibility to the Image component.

Edit ImageFragment as follows:

text
const ImageFragment = graphql`
  fragment ImageFragment on Image {
    url
    // change-line
    altText
  }
`;

Now, without editing Story, Newsfeed, or any other component, all of the images within our query will have alt text fetched for them. So we just need to modify Image to use the new field:

text
function Image({image}) {
  // ...
  <img
    // change-line
    alt={data.altText}
  //...
}

Now both the story thumbnail image and the poster’s profile pic will have an alt text. (You can use your browser’s Elements inspector to verify this.)

You can imagine how beneficial this is as your codebase gets larger. Each component is self-contained, no matter how many places it’s used in! Even if a component is used in hundreds of places, you can add or remove fields from its data dependencies at will. This is one of the main ways that Relay helps you scale with the size of your app.

Fragments are the building blocks of Relay apps. As such, a lot of Relay features are based on fragments. We’ll look at a few of them in the next sections.


Fragment arguments and field arguments

Currently the Image component fetches images at their full size, even if they’ll be displayed at a smaller size. This is inefficient! The Image component takes a prop that says what size to show the image at, so it’s controlled by the component that uses Image. We’d like in a similar way for the component that uses Image to say what size of image to fetch within its fragment.

GraphQL fields can accept arguments that give the server additional information to fulfill our request. For example, the url field on the Image type accepts height and width arguments that the server incorporates into the URL — if we have this fragment:

text
fragment Example1 on Image {
  url
}

we might get the URL such as /images/abcde.jpeg

— whereas if we have this fragment:

text
fragment Example2 on Image {
  url(height: 100, width: 100)
}

we might get a URL like /images/abcde.jpeg?height=100&width=100

Now of course, we don’t want to just hard-code a specific size into ImageFragment, because we’d like the Image component to fetch a different size in different contexts. To do that, we can make the ImageFragment accept fragment arguments so that the parent component can specify how large of an image should be fetched. These fragment arguments can then be passed into specific fields (in this case url) as field arguments.

To do that, edit ImageFragment as follows:

text
const ImageFragment = graphql`
  fragment ImageFragment on Image
    @argumentDefinitions(
      // color1
      width: {
        // color2
        type: "Int",
        // color3
        defaultValue: null
      }
      height: {
        type: "Int",
        defaultValue: null
      }
    )
  {
    url(
      // color4
      width: $width,
      // color4
      height: $height
    )
    altText
  }
`;

Let’s break this down:

  • We’ve added an @argumentDefinitions directive to the fragment declaration. This says what arguments the fragment accepts. For each argument, we give:
    • The name of the argument
    • Its type (which can be any GraphQL scalar type)
    • Optionally a default value — in this case, the default value is null, which lets us fetch the image at its inherent size. If no default value is given, then the argument is required at every place the fragment is used.
  • Then we populate an argument to a GraphQL field by using the fragment argument as a variable. Here the field arguments and fragment arguments have the same name (as will often be the case), but note: width: is the field argument while $width is the variable created by the fragment argument.

Now the fragment accepts an argument that it passes along to the server via one of the fields it selects.

Deep dive: GraphQL Directives

The syntax for fragment arguments may look rather clumsy. This is because it is based on directives, a system for extending the GraphQL language. In GraphQL, any symbol starting with @ is a directive. Their meaning isn't defined by the GraphQL spec, but is up to the specific client or server implementation.

Relay defines several directives to support its features — fragment arguments for one. These directives are not sent to the server, but give instructions to the Relay compiler at build time.

The GraphQL spec actually does define the meaning of three directives:

  • @deprecated is used in schema definitions and marks a field as deprecated.
  • @include and @skip can be used to make the inclusion of a field conditional.

Besides these, GraphQL servers can specify additional directives as part of their schemas. And Relay has its own build-time directives, which allow us to extend the language a bit without changing its grammar.


Now the different fragments using Image can pass in the appropriate size for each image:

text
const StoryFragment = graphql`
  fragment StoryFragment on Story {
    title
    summary
    postedAt
    poster {
      ...PosterBylineFragment
    }
    thumbnail {
      // change-line
      ...ImageFragment @arguments(width: 400)
    }
  }
`;
text
const PosterBylineFragment = graphql`
  fragment PosterBylineFragment on Actor {
    name
    profilePicture {
      // change-line
      ...ImageFragment @arguments(width: 60, height: 60)
    }
  }
`;

Now if you look at the images that our app downloads, you’ll see they’re of the smaller size, saving network bandwidth. Note that although we used integer literals for the value of our fragment arguments, we can also use variables supplied at runtime, as we'll see in later sections.

Field arguments (e.g. url(height: 100)) are a feature of GraphQL itself, while fragment arguments (as in @argumentDefinitions and @arguments) are Relay-specific features. The Relay compiler processes these fragment arguments when it combines fragments into queries.


Summary

Fragments are the most distinctive aspect of how Relay uses GraphQL. We recommend that every component that displays data and cares about the semantics of that data (so not just a typographic or formatting component) use a GraphQL fragment to declare its data dependencies.

  • Fragments help you scale: No matter how many places a component is used, you can update its data dependencies in a single place.
  • Fragment data needs to be read out with useFragment.
  • useFragment takes a fragment key which says where in the graph to read from.
  • Fragment keys come from places in a GraphQL response where that fragment was spread.
  • Fragments can define arguments which are used at the point they’re spread. This allows them to be tailored to each situation they're used in.

We'll be revisiting many other features of fragments, such as how to refetch the contents of a single fragment without refetching the entire query. First, though, let's make this newsfeed app more newsfeed-like by learning about arrays.


Tutorial Graphql (`website/docs/tutorial/graphql.mdx`)

GraphQL and Relay

This section is an overview to situate Relay in relation to GraphQL, React, and the other parts of the stack. Don’t worry about understanding every detail, just try to get the gist and then proceed to the next section to start working with code. Much more specifics will be explained as we go through working examples throughout the tutorial.

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';


GraphQL is a language for querying and modifying data on servers. The unique thing about GraphQL is that rather than having a fixed set of API endpoints, your server provides a palette of options that the client can use to request any combination of data that it may need. This allows front-end developers to move more quickly because there is no need to write and deploy new endpoints as data requirements change. It also means that when a new version of the client is released, it can request just the data it needs, without extra fields leftover for compatibility with older versions.

GraphQL provides a unified interface for querying data across any kind of back-end. Whether your data is in a relational SQL database, a graph-oriented database, or an armada of microservices, a GraphQL server can collect the data from multiple back-ends and send it to the client in a single response, which is more efficient than issuing separate queries to each service from the client.

In a traditional HTTP API, there are URLs that each respond with a fixed set of information:

text
Request:
GET /person?id=24601

Response:
{"id": "24601", "name": "Jean Valjean", "age": 64, "occupation": "Mayor"}

In GraphQL, the client asks for the specific information that it wants, and the server responds with just the information that was requested:

text
Request:
query {
  person(id: "24601") {
    name
    occupation
  }
}

Response:
{
  "person": {
    "name": "Jean Valjean",
    "occupation": "Mayor"
  }
}

Notice that only the specific fields that the client requested were included in the response.

As the name suggests, GraphQL organizes data into a graph. The graph consists of nodes (like objects or records) and edges (pointers from one node to another):

GraphQL lets you follow those edges from one node to another and ask for information about each node that you visit. For example, here we go from a person to their city and get information about the city:

text
query {
  person(id: "24601") {
    name
    occupation
    location {
      name
      population
    }
  }
}
text
{
  "person": {
    "name": "Jean Valjean",
    "occupation": "Mayor",
    "location": {
      "name": "Montreuil-sur-Mer",
      "population": 1935
    }
  }
}

This means we can retrieve information about a whole panoply of objects all in one query — in other words, you can efficiently get all the data for a screen in a single request instead of sending many requests one after the other. But you achieve this without writing and maintaining a separate endpoint for each screen in your UI.

Instead, your GraphQL server provides a schema, which describes what kinds of nodes there are, how they’re connected, and what information each node contains. Then, you pick and choose from this schema to select the information you want.

The example app in this tutorial is a newsfeed app, so its schema consists of types such as

  • Story, which represents a newsfeed story — it has fields such as its title, an image, and an edge to the person or organization who posted it
  • Person, with information such as their name, email, and a list of friends (which are edges to other Persons).
  • Viewer, which represents the person viewing the app and has information like their list of newsfeed stories
  • Image, which has a URL for the image itself as well as an alt text description.

The GraphQL language includes a type system and language for specifying the schema. Here’s a snippet from the schema definition for our example app — don’t worry about every detail, it’s just to give you a general idea:

text
// A newsfeed story. It has fields, some of which are scalars (e.g. strings
// and numbers) and some that are edges that point to other nodes in the graph,
// such as the 'thumbnail' and 'poster' fields:
type Story {
  id: ID!
  category: Category
  title: String
  summary: String
  thumbnail: Image
  poster: Actor
}

// An Actor is an entity that can do something on the site. This is an
// interface that multiple different types can implement, in this case
// Person and Organization:
interface Actor {
  id: ID!
  name: String
  profilePicture: Image
}

// This is a specific type that implements that interface:
type Person implements Actor {
  id: ID!
  name: String
  email: String
  profilePicture: Image
  location: Location
}

// The schema also lets you define enums, such as the category
// of a newsfeed story:
enum Category {
  EDUCATION
  NEWS
  COOKING
}

Besides queries, GraphQL also lets you send mutations that ask the server to update its data. If queries are analogous to HTTP GET requests, then mutations are the equivalent of POST requests. Like POSTs, they let the server respond with updated data. GraphQL also has subscriptions which allow for an open connection for realtime updates.

(GraphQL is usually implemented over HTTP, so queries and mutations are not only analogous to GET and POST, but may be sent as such as well.)


Now that we’ve talked about GraphQL, let’s talk about Relay. It has a few different parts and pieces that we’ll briefly go over before diving into the code.

Relay is a data management library for the client that’s oriented around GraphQL, but uses it in a very specific way that gets the most benefit from it.

For the best performance, you want your app to issue a single request at the beginning of each screen or page instead of having individual components issue their own requests. But the problem with that is that it couples components and screens together, creating a big maintenance problem: If you need some additional data in a specific component, you have to find every screen where that component is used and add the new field to that screen’s query. On the other hand, if you remove the need for a particular field, you have to remove that field from every query again — but this time, are you sure the field isn’t still in use by some other component? It becomes very difficult to maintain these big screen-wide queries.

Relay’s unique strength is to avoid this tradeoff by letting each component declare its own data requirements locally, but then stitching those requirements together into larger queries. That way you get both performance and maintainability.

Relay does this with a compiler that scans your JavaScript code for fragments of GraphQL, and then stitches those fragments together into complete queries.

Besides the compiler, Relay has runtime code that manages the fetching and processing of GraphQL. It maintains a local cache of all the data that has been retrieved (called the Store), and vends out to each component the data that belongs to it:

The advantage of having a centralized Store is that it lets you keep your data consistent when it’s updated. For instance, if your UI has a way for somebody to edit their name, then you can make that update in a single place and every component that displays that person’s name will see the new information, even if they’re on different screens and therefore used different queries to initially retrieve the data. This is because Relay normalizes the data as it comes in, meaning that it merges all the data it sees for a single graph node into one place, so it doesn’t have multiple copies of the same node.

Indeed, Relay doesn’t just query data, it provides functions to manage the entire lifecycle of querying and updating, including support for optimistic updates and rollbacks. You can paginate, refresh data — all of the basic operations you’ll need to create a UI. Whenever data in the Store is updated, Relay efficiently re-renders just those components that are displaying that particular data.

Summary

GraphQL is a language for modeling data as a graph and querying that data from a server (as well as updating the data). Relay is a React-based client library for GraphQL that lets you build up queries from individual fragments that are co-located with each React component. Once the data has been queried, Relay maintains consistency and re-renders components as the data is updated.


Tutorial Interfaces Polymorphism (`website/docs/tutorial/interfaces-polymorphism.mdx`)

GraphQL Types, Interfaces, and Polymorphism

In this section, we’ll see how to treat different types of nodes differently. You might notice that some of the newsfeed stories in the example app are posted by people, while others are posted by organizations. In this example, we'll enhance our hovercard by writing a fragment that selects people-specific information about people and organization-specific information about organizations.


We’ve alluded to the fact that GraphQL nodes aren’t just random bags of fields — they have types. Your GraphQL schema defines what fields each type has. For instance, it might define the Story type like this:

text
type Story {
  id: ID!
  title: String
  summary: String
  createdAt: Date
  poster: Actor
  image: Image
}

Here, some of the fields are scalars (like String and ID). Others are types defined elsewhere in the schema, like Image — these fields are edges to nodes of those specific types. The ! on ID! means that field is non-nullable. In GraphQL, fields are normally nullable and non-nullability is the exception.

Fragments are always “on” a particular type. In our example above, StoryFragment is defined on Story. This means that you can only spread it into places in a query where a Story node is expected. And it means that the fragment can select just those fields that exist on the Story type.

Of particular interest is the Actor type used for the poster field. This type is an interface. That means that the poster of a story can be a Person, a Page, an Organization, or any other type of entity that meets the specifications for being an “Actor”.

The GraphQL schema in our example app defines an Actor as follows:

text
interface Actor {
  name: String
  profilePicture: Image
  joined: DateTime
}

Not coincidentally this is exactly the information that we’re displaying here. There are two types in the schema that implement Actor, meaning that they include all the fields defined in Actor. They are declared as such:

text
type Person implements Actor {
  id: ID!
  name: String
  profilePicture: Image
  joined: DateTime
  email: String
  location: Location
}

type Organization implements Actor {
  id: ID!
  name: String
  profilePicture: Image
  joined: DateTime
  organizationKind: OrganizationKind
}

Both of these types have name , profilePicture, and joined, so they can both declare that they implement Actor and thus can be used wherever an Actor is called for in the schema and in fragments. They also have other fields that are distinct to each particular type.

Let’s see how to work with interfaces more by extending the PosterDetailsHovercardContentsBody component to display the location of a Person or the organization kind of an Organization. These are fields that are only present on those specific types, not on the Actor interface.

Right now, if you’ve followed along so far, it should have a fragment defined like this (in PosterDetailsHovercardContents.tsx):

text
fragment PosterDetailsHovercardContentsBodyFragment on Actor {
  name
  joined
  profilePicture {
    ...ImageFragment
  }
}

If you try to add a field like organizationKind to this fragment, you’ll get an error from the Relay compiler:

text
✖︎ The type `Actor` has no field organizationKind

This is because when we define a fragment as being on an interface, we can only use fields from that interface. To use fields from a specific type that implements the interface, we use a type refinement to tell GraphQL we’re selecting fields from that type:

text
fragment PosterDetailsHovercardContentsBodyFragment on Actor {
  name
  joined
  profilePicture {
    ...ImageFragment
  }
  // change
  ... on Organization {
    organizationKind
  }
  // end-change
}

Go ahead and add this now. You can also add a type refinement for Person:

text
fragment PosterDetailsHovercardContentsBodyFragment on Actor {
  name
  joined
  profilePicture {
    ...ImageFragment
  }
  ... on Organization {
    organizationKind
  }
  // change
  ... on Person {
    location {
      name
    }
  }
  // end-change
}

When you select a field that’s only present on some of the types that implement an interface, and the node you’re dealing with is of a different type, then you simply get null for the value of that field when you read it out. With that in mind, we can modify the PosterDetailsHovercardContentsBody component to show the location of people and organization kind of organizations:

text
import OrganizationKind from './OrganizationKind';

function PosterDetailsHovercardContentsBody({ poster }: Props): React.ReactElement {
  const data = useFragment(PosterDetailsHovercardContentsBodyFragment, poster);
  return (
    <>
      <Image image={data.profilePicture} width={128} height={128} className="posterHovercard__image" />
      <div className="posterHovercard__name">{data.name}</div>
      <ul className="posterHovercard__details">
         <li>Joined <Timestamp time={poster.joined} /></li>
         // change
         {data.location != null && (
           <li>{data.location.name}</li>
         )}
        {data.organizationKind != null && (
          <li><OrganizationKind kind={data.organizationKind} /></li>
         )}
         // end-change
      </ul>
    </>
  );
}

You should now see the location of people, and the organization kind for organizations:

By the way, we can now understand why we had ... on Actor in the example earlier. The node field can return a node of any type because any ID could be given at runtime. So the type that it gives us is Node, a very generic interface that can be implemented by anything that has an id field. We needed a type refinement to use fields from the Actor interface.

:::note
In the GraphQL spec and other sources, type refinements are called inline fragments. We call them “type refinements” instead because this terminology is more descriptive and less confusing.
:::

:::tip
If you need to do something totally different depending on what type it is, you can select a field called __typename, which returns a string with the name of the concrete type that you got (e.g., "Person" or "Organization"). This is a built-in feature of GraphQL.
:::

Summary

The ... on Type {} syntax allows us to select fields that are only present in a specific type that implements an interface.


Tutorial Mutations Updates (`website/docs/tutorial/mutations-updates.mdx`)

Mutations & Updates

In this chapter we’ll learn how to update data on the server and client. We’ll go through two main examples:

  • Implementing a Like button for newsfeed stories
  • Implementing the ability to post comments on a newsfeed story

Updating data is a complicated problem domain, and Relay handles many aspects of it automatically, while also giving you a lot of manual control so that your app can be as robust as possible in handling the cases that can arise.

First let’s distinguish two terms:

  • A mutation is when you ask the server to perform some action that modifies data on the server. This is a feature of GraphQL that is analogous to an HTTP POST.
  • An update is when you modify Relay’s local client-side data store.

The client doesn’t have the ability to directly manipulate individual pieces of data on the server side. Rather, mutations are opaque, high-level requests that express the user’s intent — for example, that the user friended somebody, joined a group, posted a comment, liked a particular newsfeed story, blocked somebody, or deleted a comment. (The GraphQL schema defines what mutations are available, as well as the input parameters that each mutation accepts.)

Mutations can have far-reaching and unpredictable effects on the state of the graph. For example, let’s say you join a group. Many things can change:

  • Your name gets added to the Group Members list
  • The group’s membership count gets incremented
  • The group gets added to your list of groups
  • Members-only posts appear in the group’s post feed
  • Your recommended groups may change
  • The group’s admins may get notifications
  • Many other non-user-visible effects such as logging, training models, or sending emails
  • Etc. etc. etc.

In general, it’s impossible to know what the full downstream effect of a mutation may be. So after asking the server to perform a mutation, the client just has to do its best to update its local data store, keeping the data as consistent as reasonably possible. It does this by asking the server for specific updated data as part of the mutation response, and by imperative code — called an updater — that fixes up the store to keep it consistent.

There is no principled solution that covers every case. Even if it were possible to know the full effect of a mutation on the graph, there are even circumstances where we don’t want to show updated data right away. For example, if you go to somebody’s profile page and block them, you don’t want for everything shown on that page to immediately disappear. The question of what data to update is ultimately a UI design decision.

Relay tries to make it as easy as possible to update data in response to a mutation. For example, if you want to update a certain component, you can spread its fragment into your mutation, which asks the server to send updated data for whatever was selected by that fragment. For other cases, you have to manually write an updater that modifies Relay’s local data store imperatively. We’ll look at all of these cases below.


Implementing a Like Button

Let’s dip our toes in the water by implementing the Like button for Newsfeed stories. Luckily we already have a Like button prepared, so open up Story.tsx and drop it in to the Story component, remembering to spread its fragment into Story’s fragment:

text
// change-line
import StoryLikeButton from './StoryLikeButton';

...

const StoryFragment = graphql`
  fragment StoryFragment on Story {
    title
    summary
    // ... etc
    // change-line
    ...StoryLikeButtonFragment
  }
`;

...

export default function Story({story}: Props) {
  const data = useFragment(StoryFragment, story);
  return (
    <Card>
      <PosterByline person={data.poster} />
      <Heading>{data.title}</Heading>
      <Timestamp time={data.posterAt} />
      <Image image={story.thumbnail} width={400} height={400} />
      <StorySummary summary={data.summary} />
      // change-line
      <StoryLikeButton story={data} />
      <StoryCommentsSection story={data} />
    </Card>
  );
}

Now let’s take a look at StoryLikeButton.tsx. Currently, it is a button that doesn’t do anything, along with a like count.

You can look at its fragment to see that it fetches a field likeCount for the like count and doesViewerLike to determine whether the Like button is highlighted (it’s highlighted if the viewer likes the story, i.e. if doesViewerLike is true):

text
const StoryLikeButtonFragment = graphql`
  fragment StoryLikeButtonFragment on Story {
    id
    likeCount
    doesViewerLike
  }
`;

We want to make it so that when you press the Like button

  1. The story gets “liked” on the server
  2. Our local client copy of the “likeCount” and “doesViewerLike” fields get updated.

To do that, we need to write a GraphQL mutation. But first...

Anatomy of a Mutation

The GraphQL mutation syntax is going to be confusing unless you first understand the following:

GraphQL has two different request types, Queries and Mutations — and they work in exactly the same way. This is analogous to how HTTP has GET and POST: technically the only difference is that POST requests are intended to cause effects while GET requests do not. Similarly, a Mutation is exactly like a Query, except that the Mutation is expected to cause things to happen. This means:

  • A mutation is part of our client code
  • A mutation declares variables that let the client pass data to the server
  • The server implements individual fields. A given mutation composes together those fields and passes its variables in as field arguments.
  • Each field yields data of a particular type — either scalar or an edge to some other graph node — which is returned to the client if that field is selected. In the case of edges, further fields are selected from the node being linked to.

The only difference is that, in a mutation, selecting a field makes something happen, as well as returning data.

The Like Mutation

With that in mind, this is what our mutation is going to look like — go ahead and add this declaration to the file:

text
const StoryLikeButtonLikeMutation = graphql`
  mutation StoryLikeButtonLikeMutation(
    // color1
    $id: ID!,
    // color1
    $doesLike: Boolean!,
  ) {
    // color2
    likeStory(
      // color3
      id: $id,
      // color3
      doesLike: $doesLike
    ) {
      // color4
      story {
        // color5
        id
        // color5
        likeCount
        // color5
        doesViewerLike
      }
    }
  }
`;

This is a lot, let’s break it down:

  • The mutation is named StoryLikeButton + Like + Mutation because it must begin with the module name, and end with the GraphQL operation.
  • The mutation declares variables which are passed from the client to the server when the mutation is dispatched. Each variable has a name ($id, $doesLike) and a type (ID!, Boolean!). The ! after the type indicates that it is required, not optional.
  • The mutation selects a mutation field defined by the GraphQL schema. Each mutation field that the server defines corresponds to some action that the client can request of the server, such as liking a story.
    • The mutation field takes arguments (just like any field can do). Here we pass in the mutation variables that we declared as the argument values — for example, the doesLike field argument is set to be the $doesLike mutation variable.
  • The likeStory field returns an edge to a node that represents the mutation response. We can select various fields in order to receive updated data. The fields that are available in the mutation response are specified by the GraphQL schema.
    • We select the story field, which is an edge to the Story that we just liked.
    • We select specific fields from within that Story to get updated data. These are the same fields that a query could select about a Story — in fact, the same fields we selected in our fragment.

When we send the server this mutation, we’ll get a response that, just like with queries, matches the shape of the mutation that we sent. For example, the server might send this back to us:

text
{
  "likeStory": {
    "story": {
      "id": "34a8c",
      "likeCount": 47,
      "doesViewerLike": true
    }
  }
}

and our job will be to update the local data store to incorporate this updated information. Relay will handle this in simple cases, while in more complex cases it will require custom code to update the store intelligently.

But we’re getting ahead of ourselves — let’s make that button trigger the mutation. Here’s what our component looks like now — we need to hook up the onLikeButtonClicked event to execute StoryLikeButtonLikeMutation.

text
function StoryLikeButton({story}) {
  const data = useFragment(StoryLikeButtonFragment, story);
  function onLikeButtonClicked() {
    // To be filled in
  }
  return (
    <>
      <LikeCount count={data.likeCount} />
      <LikeButton value={data.doesViewerLike} onChange={onLikeButtonClicked} />
    </>
  )
}

To do that, we add a call to useMutation:

text
// change-line
import {useMutation, useFragment} from 'react-relay';

function StoryLikeButton({story}) {
  const data = useFragment(StoryLikeButtonFragment, story);
  // change-line
  const [commitMutation, isMutationInFlight] = useMutation(StoryLikeButtonLikeMutation);
  function onLikeButtonClicked() {
    // change
    commitMutation({
      variables: {
        id: data.id,
        doesLike: !data.doesViewerLike,
      },
    })
    // end-change
  }
  return (
    <>
      <LikeCount count={data.likeCount} />
      <LikeButton value={data.doesViewerLike} onChange={onLikeButtonClicked} />
    </>
  )
}

The useMutation hook returns a function commitMutation that we can call to tell the server to do stuff.

We pass in an option called variables where we give values for the variables defined by the mutation, namely id and doesViewerLike. This tells the server which story we’re talking about and whether the we are liking or un-liking it. The id of the story we’ve read from the fragment, while whether we like it or unlike it comes from toggling whatever the current value that we rendered is.

The hook also returns a boolean flag that tells us when the mutation is in flight. We can use that to make the user experience nicer by disabling the button while the mutation is happening:

text
<LikeButton
  value={data.doesViewerLike}
  onChange={onLikeButtonClicked}
  // change-line
  disabled={isMutationInFlight}
/>

With this in place, we should now be able to like a story!

How Relay Automatically Processes Mutation Responses

But how did Relay know to update the story we clicked on? The server sent back a response with this form:

text
{
  "likeStory": {
    "story": {
      "id": "34a8c",
      "likeCount": 47,
      "doesViewerLike": true
    }
  }
}

Whenever the response includes an object with an id field, Relay will check if the store already contains a record with a matching ID in the id field of that record. If there is a match, Relay will merge the other fields from the response into the existing record. That means that, in this simple case, we don’t need to write any code to update the store.


Using Fragments in the Mutation Response

Remember that mutations are just like queries. In order to make sure that the mutation response always contains the data we want to render, instead of having a separate set of fields that has to be manually kept up to date, we can simply spread fragments into our mutation response:

text
const StoryLikeButtonLikeMutation = graphql`
  mutation StoryLikeButtonLikeMutation(
    $id: ID,
    $doesLike: Boolean,
  ) {
    likeStory(id: $id, doesLike: $doesLike) {
      story {
        // change-line
        ...StoryLikeButtonFragment
      }
    }
  }
`;

Now if we add or remove data requirements, all of the necessary data (but no more) will be included with the mutation response. This is usually the smart way of writing mutation responses. You can spread in any fragment from any component, not just the component that triggers the mutation. This helps you keep your whole UI up to date.


Improving the UX with an Optimistic Updater

Mutations take time to perform, yet we always want the UI to update immediately in some way to give the user feedback that they’ve taken an action. In the current example, the Like button is disabled while the mutation is happening, and then after the mutation is done, the UI is updated into the new state as Relay merges the updated data into its store and re-renders the affected components.

Oftentimes the best feedback will be to simply pretend the operation is already completed: for example, if you press the Like button, that buttons immediately goes into the same highlighted state that it will stay in whenever you see something you’ve already liked. Or take the example of posting a comment: We would like to immediately show your comment as having been posted. This is because mutations are usually fast and reliable enough that we don’t need to bother the user with a separate loading state for them. However, sometimes mutations do end in failure. In that case, we’d like to roll back the changes we made and return you to the state you were in before we tried the mutation: the comment we showed as being posted should go away, while the text of the comment should re-appear within the composer where you wrote it, so that the data isn’t lost if you want to try posting again.

Managing these so-called optimistic updates is complicated to do manually, but Relay has a robust system for applying and rolling back updates. You can even have multiple mutations in flight at the same time (say if the user clicks several buttons in sequence), and Relay will keep track of what changes need to be rolled back in case of a failure.

Mutations proceed in three phases:

  • First there’s an optimistic update, where you update the local data store into whatever state you anticipate and want to show to the user immediately.
  • Then you actually perform the mutation on the server. If it’s successful, the server responds with updated information which can be used in the third step.
  • When the mutation is done, you roll back the optimistic update. If the mutation failed, you’re done — back to where you started. If the mutation succeeded, Relay merges simple changes into the store and then applies a concluding update that updates the local data store with the actual new information received from the server, plus whatever other changes you want to make.

With this background knowledge in hand, let’s go ahead and write an optimistic updater for our Like button so that it immediately updates to the new state when clicked.

Step 1 — Add the optimisticUpdater option to commitMutation

Go to StoryLikeButton and add a new option to the call to commitMutation:

text
function StoryLikeButton({story}) {
  ...
  function onLikeButtonClicked(newDoesLike) {
    commitMutation({
      variables: {
        id: data.id,
        doesLike: newDoesLike,
      },
      // change
      optimisticUpdater: store => {
        // TODO fill in optimistic updater
      },
      // end-change
    })
  }
  ...
}

This callback receives a store argument which represents Relay’s local data store. It has various methods for reading and writing local data. All of the writes that we make in the optimistic updater will be applied immediately when the mutation is dispatched, and then rolled back when it is complete.

Step 2 — Create an Updatable Fragment

We can read and write data in the local store by writing a special kind of fragment called an updatable fragment. Unlike a regular fragment, it doesn’t get spread into queries and sent to the server. Instead, it lets us read data out of the local store using the same GraphQL syntax we already know and love. Go ahead and add this fragment definition:

text
function StoryLikeButton({story}) {
  ...
      optimisticUpdater: store => {
        const fragment = graphql`
          fragment StoryLikeButton_updatable on Story
            // color1
            @updatable
          {
            likeCount
            doesViewerLike
          }
        `;
      },
  ...
}

It’s exactly like any other fragment but annotated with the @updatable directive.

Unlike normal fragments, updatable fragments are not spread into queries and do not select data to be fetched from the server. Instead, they select data that’s already in the Relay local data store so that the data may be updated.

Step 3 — Call readUpdatableFragment

We pass this fragment, along with the original fragment ref that we received as a prop (which tells us which story we’re liking), to store.readUpdatableFragment. It returns a special object called updatableData:

text
function StoryLikeButton({story}) {
  ...
      optimisticUpdater: store => {
        const fragment = graphql`
          fragment StoryLikeButton_updatable on Story @updatable {
            likeCount
            doesViewerLike
          }
        `;
        const {
          // color1
          updatableData
        } = store.readUpdatableFragment<StoryLikeButton_updatable$key>(
          // color2
          fragment,
          // color3
          story
        );
      },
  ...
}

Step 4 — Modify the Updatable Data

Now updatableData is an object representing our existing Story as it exists in the local store. We can read and write the fields listed in our fragment:

text
function StoryLikeButton({story}) {
  ...
      optimisticUpdater: store => {
        const fragment = graphql`
          fragment StoryLikeButton_updatable on Story @updatable {
            likeCount
            doesViewerLike
          }
        `;
        const { updatableData } =
          store.readUpdatableFragment<StoryLikeButton_updatable$key>(
            fragment,
            story
          );
        // change
        const alreadyLikes = updatableData.doesViewerLike;
        updatableData.doesViewerLike = !alreadyLikes;
        updatableData.likeCount += (alreadyLikes ? -1 : 1);
        // end-change
      },
  ...
}

In this example, we toggle doesViewerLike (so that clicking the button when you already like the story makes you un-like it) and increment or decrement the like count accordingly.

Relay records the changes we made to updatableData and will roll them back once the mutation is complete.

Now when you click the Like button, you should see the UI immediately update.


Adding Comments — Mutations on Connections

The only thing that Relay can do completely automatically is what we’ve seen already: merge nodes in the mutation response with existing nodes that share the same ID in the store. For anything else, we have to give Relay some more information.

Let’s look at the case of Connections. We’ll implement the ability to post a new comment on a story.

The server’s mutation response only includes the newly-created comment. We have to tell Relay how to insert that story into the Connection between a story and its comments.

Head back over to StoryCommentsSection and add a component for posting a new comment, remembering to spread its fragment into our fragment:

text
// change-line
import StoryCommentsComposer from './StoryCommentsComposer';

const StoryCommentsSectionFragment = graphql`
  fragment StoryCommentsSectionFragment on Story
    ...
  {
    comments(after: $cursor, first: $count)
      @connection(key: "StoryCommentsSectionFragment_comments")
    {
      ...
    }
    // change-line
    ...StoryCommentsComposerFragment
  }
`

function StoryCommentsSection({story}) {
  ...
  return (
    <>
      // change-line
      <StoryCommentsComposer story={data} />
      ...
    </>
  );
}

We should now see a composer at the top of the comments section:

Now take a look inside StoryCommentsComposer.tsx:

text
function StoryCommentsComposer({story}) {
  const data = useFragment(StoryCommentsComposerFragment, story);
  const [text, setText] = useState('');
  function onPost() {
   // TODO post the comment here
  }
  return (
    <div className="commentsComposer">
      <TextComposer text={text} onChange={setText} onReturn={onPost} />
      <PostButton onClick={onPost} />
    </div>
  );
}

Step 1 — Define the Comment Posting Mutation

Just like before, we need to define a mutation. It will send to the server the story ID and the text of the comment to be added:

text
const StoryCommentsComposerPostMutation = graphql`
  mutation StoryCommentsComposerPostMutation(
    $id: ID!,
    $text: String!,
  ) {
    postStoryComment(id: $id, text: $text) {
      commentEdge {
        node {
          id
          text
        }
      }
    }
  }
`;

Here, the schema allows us to select as part of the mutation response the newly-created edge to the newly-created comment. We select it and will use it to update the local store by inserting this edge into the Connection.

Step 2 — Call commitMutation to post it

Now we use the useMutation hook to get access to the commitMutation callback, and call it in onPost:

text
function StoryCommentsComposer({story}) {
  const data = useFragment(StoryCommentsComposerFragment, story);
  const [text, setText] = useState('');
  // change-line
  const [commitMutation, isMutationInFlight] = useMutation(StoryCommentsComposerPostMutation);
  function onPost() {
    // change
    setText(''); // Reset the UI
    commitMutation({
      variables: {
        id: data.id,
        text,
      },
    })
    // end-change
  }
  ...
}

Step 3 — Add a Declarative Connection Handler

At this point, we can find from the network logs that clicking Post will send a mutation request to the server — you can even see that the comment has been posted since it appears if you refresh the page. However, nothing happens in the UI. We need to tell Relay to append the newly-created comment to the Connection that goes from the story to its comments.

You’ll notice that in the mutation response that we wrote above we select commentEdge. This is the edge to the newly-created comment. We just need to tell Relay what Connections to add that edge to. Relay provides directives called @appendEdge, @prependEdge, and @deleteEdge that you put on the edge in the mutation response. Then when you run the mutation, you pass in the IDs of the Connections that you want to modify. Relay will append, prepend, or delete the edge from those connections as you specify.

We want our newly-created comment to appear at the top of the list, so we’ll use @prependEdge. Make the following additions to the mutation definition:

text
mutation StoryCommentsComposerPostMutation(
    $id: ID!,
    $text: String!,
    // change-line
    $connections: [ID!]!,
  ) {
    postStoryComment(id: $id, text: $text) {
      commentEdge
        // change-line
        @prependEdge(connections: $connections)
      {
        node {
          id
          text
        }
      }
    }
  }

We’ve added a variable called connections to the mutation. We’ll use this to pass in the Connections we want to update.

:::note
The $connections variable is only used as an argument to the @prependEdge directive, which is processed on the client by Relay. Because $connections is not passed as an argument to any field, it is not sent to the server.
:::

Step 4 — Pass in the Connection ID as a Mutation Variable

We need to identify the Connection to add the new edge to. A Connection is identified with two pieces of information:

  • Which node it’s off of — in this case, the Story we’re posting a comment to.
  • The key provided in the @connection directive, which lets us distinguish connections in case more than one connection is off of the same node.

We pass this information in to the mutation variables using a special API provided by Relay:

text
// change-line
import {useFragment, useMutation, ConnectionHandler} from 'react-relay';

...

export default function StoryCommentsComposer({story}: Props) {
  ...
  function onPost() {
    setText('');
    // change
    const connectionID = ConnectionHandler.getConnectionID(
      data.id,
      'StoryCommentsSectionFragment_comments',
    );
    // end-change
    commitMutation({
      variables: {
        id: data.id,
        text,
        // change-line
        connections: [connectionID],
      },
    })
  }
  ...
}

The string "StoryCommentsSectionFragment_comments" that we pass to getConnectionID is the identifier that we used when fetching the connection in StoryCommentSection — just as a reminder, here's what that looked like:

text
const StoryCommentsSectionFragment = graphql`
  fragment StoryCommentsSectionFragment on Story
  ...
  {
    comments(after: $cursor, first: $count)
     @connection(key: "StoryCommentsSectionFragment_comments")
    {
      ...
  }
`;

Meanwhile, the argument data.id is the ID of the specific story that we’re connecting off of.

With this change, we should see the comment appear in the list of comments once the mutation is complete.


Summary

Mutations let us ask the server to make changes.

  • Just like Queries, Mutations are composed of fields, accept variables, and pass those variables as arguments to fields.
  • The fields that are selected by a Mutation constitute the mutation response which we can use to update the Store.
  • Relay automatically merges nodes in the response to nodes in the Store with matching IDs.
  • The @appendEdge, @prependEdge, and @deleteEdge directives let us insert and remove items from the mutation response into Connections in the store.
  • Updaters let us manually manipulate the store.
  • Optimistic updaters run before the mutation begins and are rolled back when it is done.

Tutorial Organizing Mutations Queries And Subscriptions (`website/docs/tutorial/organizing-mutations-queries-and-subscriptions.mdx`)

Organizing GraphQL Operations

In Relay, GraphQL operation and fragment names must be globally unique and must end with the GraphQL operation type (e.g. Query, Mutation, Subscription).

Put Mutations in their own hook module so the name is closer to what the mutation does rather than which component invokes it. If the module name is correctly descriptive, it is fine to declare it in the same file.

If you are adding a Mutation for Post, like adding a comment to a post, you may create a new file titled useAddPostComment.js. Your mutation (in this file) will then be named useAddPostCommentMutation, which is a perfectly descriptive name.

Root components should have a single query that is tightly coupled to a component, since it describes that component's data dependencies. Queries and fragments should co-locate with their data-use code.


Tutorial Queries 1 (`website/docs/tutorial/queries-1.mdx`)

Query Basics

In this section:

  • We’ll take a React component that displays hard-coded placeholder data and modify it so that it fetches its data using a GraphQL query.
  • We’ll learn how to use the TypeScript types that Relay generates from your GraphQL to ensure type safety.

With Relay, you fetch data using GraphQL Queries. A Query specifies a part of the GraphQL graph for your app to fetch, starting from some root node and traversing from node to node to retrieve a particular set of data in the shape of a tree.

Right now, our example app doesn’t fetch any data, it just renders some placeholder data that’s hard-coded into the React components. Let’s modify it to fetch some data using Relay.

Open up the file called Newsfeed.tsx. (All of the components in the tutorial are in src/components.) In it you should see a <Newsfeed> component where the data is hard-coded:

jsx
export default function Newsfeed() {
  const story = {
    title: "Placeholder Story",
    summary:
      "Placeholder data, to be replaced with data fetched via GraphQL",
    poster: {
      name: "Placeholder Person",
      profilePicture: {
        url: "/assets/cat_avatar.png",
      },
    },
    thumbnail: {
      url: "/assets/placeholder.jpeg",
    },
  };
  return (
    <div className="newsfeed">
      <Story story={story} />
    </div>
  );
}

We’re going to replace this placeholder data with data fetched from the server. First we need to define a GraphQL query. Add the following declaration above the Newsfeed component:

text
import { graphql } from 'relay-runtime';

// color1
const NewsfeedQuery = graphql`
  // color2
  query NewsfeedQuery {
    topStory {
      // color3
      title
      // color3
      summary
      // color4
      poster {
        name
        profilePicture {
          url
        }
      }
      thumbnail {
        url
      }
    }
  }
`;

Let’s break this down:

  • To embed GraphQL within JavaScript, we put a string literal marked with the `graphql``` tag. This tag allows the Relay compiler to find and compile the GraphQL within a JavaScript codebase.
  • Our GraphQL string consists of a query declaration with the keyword query and then a query name. The query name must be globally unique and end with Query.
  • Inside the query declaration are fields, which specify what information to query for*:*
    • Some fields are scalar fields that retrieve a string, number, or other unit of information.
    • Other fields are edges that let us traverse from one node in the graph to another. When a field is an edge, it’s followed by another block { } containing fields for the node at the other end of the edge. Here, the poster field is an edge that goes from a Story to a Person who posted it. Once we’ve traversed to the Person, we can include fields about the Person such as their name.

This illustrates the part of the graph that this query is asking for:

Now that we’ve defined the query, we need to do two things.

  1. Run the Relay compiler so that it knows about the new Graphql query. (npm run relay)
  2. Modify our React component to fetch it and to use the data returned by the server.

If you open package.json you will find the script relay is hooked up to run the Relay compiler. This is what npm run relay does. Once the compiler successfully updates/generated the new compiled query you will be able to find it in the __generated__ folder under src/components/ as NewsfeedQuery.graphql.ts.

Next, turn back to the Newsfeed component and start by deleting the placeholder data. Then, replace it with this:

text
import { useLazyLoadQuery } from "react-relay";

export default function Newsfeed({}) {
  const data = useLazyLoadQuery(
    // color1
    NewsfeedQuery,
    // color2
    {},
  );
  const story = data.topStory;
  // As before:
  return (
    <div className="newsfeed">
      <Story story={story} />
    </div>
  );
}

The useLazyLoadQuery hook fetches and returns the data. It takes two arguments:

  • The GraphQL query that we defined before.
  • Variables that are passed to the server with the query. This query doesn’t declare any variables, so it’s an empty object.

The object that useLazyLoadQuery returns has the same shape as the query. For instance, if printed in JSON format it might look like this:

text
{
  topStory: {
    title: "Local Yak Named Yak of the Year",
    summary: "The annual Yak of the Year awards ceremony ...",
    poster: {
      name: "Baller Bovine Board",
      profilePicture: {
        url: '/images/baller_bovine_board.jpg',
      },
    },
    thumbnail: {
      url: '/images/max_the_yak.jpg',
    }
  }
}

Notice that each field selected by the GraphQL query corresponds to a property in the JSON response.

At this point, you should see a story fetched from the server:

:::note
The server's responses are artificially slowed down to make loading states perceptible, which will come in handy when we add more interactivity to the app. If you want to remove the delay, open server/index.js and remove the call to sleep().
:::

The useLazyLoadQuery hook fetches the data when the component is first rendered. Relay also has APIs for pre-fetching the data before your app has even loaded — these are covered later. In any case, Relay uses Suspense to show a loading indicator until the data is available.

This is Relay in its most basic form: fetching the results of a GraphQL query when a component is rendered. As the tutorial progresses, we’ll see how Relay’s features fit together to make your app more maintainable — starting with a look at how Relay generates TypeScript types corresponding to each query.

Deep dive: Suspense for Data Loading

Suspense is a new API in React that lets React wait while data is loaded before it renders components that need that data. When a component needs to load data before rendering, React shows a loading indicator. You control the loading indicator's location and style using a special component called Suspense.

Right now, there's a Suspense component inside App.tsx, which is what shows the spinner while useLazyLoadQuery is loading data.

We'll look at Suspense in more detail in later sections when we add some more interactivity to the app.

Deep dive: Queries are Static

All of the GraphQL strings in a Relay app are pre-processed by the Relay compiler and removed from the resulting bundled code. This means you can’t construct GraphQL queries at runtime — they have to be static string literals so that they’re known at compile time. But it comes with major advantages.

First, it allows Relay to generate type definitions for the results of the query, making your code more type-safe.

Second, Relay replaces the GraphQL string literal with an object that tells Relay what to do. This is much faster than using the GraphQL strings directly at runtime.

Also, Relay’s compiler can be configured to save queries to the server when you build your app, so that at runtime the client need only send a query ID instead of the query itself. This saves bundle size and network bandwidth, and can prevent attackers from writing malicious queries since only those your app was built with need be available.

So when you have a GraphQL tagged string literal in your program...

text
const MyQuery = graphql`
  query MyQuery {
    viewer {
      name
    }
  }
`;

... the JavaScript variable MyQuery is actually assigned to an object that looks something like this:

text
const MyQuery = {
  kind: "query",
  selections: [
    {
      name: "viewer",
      kind: "LinkedField",
      selections: [
        name: "name",
        kind: "ScalarField",
      ],
    }
  ]
};

along with various other properties and information. These data structures are carefully designed to allow the JIT to run Relay’s payload processing code very quickly. If you’re curious, you can use the Relay Compiler Explorer to play with it.


Relay and the Type System

You might notice that TypeScript reports an error with this code as we’ve written it:

text
const story = data.topStory;
                   ^^^^^^^^
Property 'topStory' does not exist on type 'unknown'

To fix this, we need to annotate the call to useLazyLoadQuery with types that Relay generates. That way, TypeScript will know what type data should have based on the fields we’ve selected in our query. Add the following:

text
// change-line
import type {NewsfeedQuery as NewsfeedQueryType} from './__generated__/NewsfeedQuery.graphql';

function Newsfeed({}) {
  const data = useLazyLoadQuery
  // change-line
  <NewsfeedQueryType>
  (NewsfeedQuery, {});
  ...
}

If we look inside __generated__/NewsfeedQuery.graphql we’ll see the following type definition — with the annotation we’ve just added, TypeScript knows that data should have this type:

text
export type NewsfeedQuery$data = {
  readonly topStory: {
    readonly poster: {
      readonly name: string | null;
      readonly profilePicture: {
        readonly url: string;
      } | null;
    };
    readonly summary: string | null;
    readonly thumbnail: {
      readonly url: string;
    } | null;
    readonly title: string;
  } | null;
};

The Relay compiler generates TypeScript types corresponding to every piece of GraphQL that you have in your app within a graphql``` literal. As long as npm run dev` is running, the Relay compiler will automatically regenerate these files whenever you save one of your JavaScript source files, so you don’t need to refresh anything to keep them up to date.

Using Relay’s generated types makes your app safer and more maintainable. In addition to TypeScript, Relay supports the Flow type system if you want to use that instead. When using Flow, the extra annotation on useLazyLoadQuery is not needed, because Flow directly understands the contents of the `graphql``` tagged literal.

We’ll revisit types throughout this tutorial. But next, we'll look at an even more important way that Relay helps us with maintainability.


Summary

Queries are the foundation of fetching GraphQL data. We’ve seen:

  • How to define a GraphQL query within our app using the `graphql``` tagged literal.
  • How to use the useLazyLoadQuery hook to fetch the results of a query when a component renders.
  • How to import Relay's generated types for type safety.

In the next section, we’ll look at Fragments, one of the most core and distinctive aspects of Relay. Fragments let each individual component define its own data requirements, while retaining the performance advantages of issuing a single query to the server.


Tutorial Queries 2 (`website/docs/tutorial/queries-2.mdx`)

Queries for Interactions

We’ve seen how fragments let us specify data requirements in each component, yet at runtime perform only a single query for an entire screen. Here we’ll look at a situation where we want a second query on the same screen. This will also let us explore some more features of GraphQL queries.

  • We’ll build a hovercard that shows more details about the poster of a story when you hover over their name.
  • The hovercard will use a second query to fetch additional information that’s only needed if the user hovers.
  • We’ll use query variables to tell the server which person we’d like more details about.
  • We’ll see how to improve performance with preloaded queries.

After covering these topics, we’ll return to look at some more advanced features of Fragments.


In this section we’ll add a hovercard to PosterByline so that you can see more details about the poster of a story by hovering over their name.

Deep dive: When to use a secondary query

We've mentioned before that Relay is designed to help you fetch all of your data requirements for an entire screen up-front. But we can generalize this and say that it's a user interaction that should have at most one query. Navigating to another screen is just one common type of user interaction.

Within a screen, some interactions may disclose additional data from what was shown initially. If an interaction is performed relatively rarely, but needs a significant amount of additional data, it can be smart to fetch that additional data in a second query, performed when the interaction happens, rather than up-front when the screen is first loaded. This makes that initial load faster and less expensive.

There are also some interactions where the amount of data fetched is indefinite — e.g., a hovercard within a hovercard — and not feasible to know statically.

If data is lower-priority and should be loaded after the main data has loaded, but should pop in automatically without further user input, Relay has a feature called a deferred fragment for that. We'll cover it later.

We’ve already prepared a hovercard component that you can put to use. However, it has been in a directory called future in order to avoid compilation errors since it uses ImageFragment. Now that we’re at this stage of the tutorial, you can move the modules in future into src/components:

shell
mv future/* src/components

Now, if you did the exercise to make PosterByline use fragments, the PosterByline component should look something like this:

text
export default function PosterByline({ poster }: Props): React.ReactElement {
  const data = useFragment(PosterBylineFragment, poster);
  return (
    <div className="byline">
      <Image image={data.profilePicture} width={60} height={60} className="byline__image" />
      <div className="byline__name">{data.name}</div>
    </div>
  );
}

To use the hovercard component, you can make the following changes:

text
// change-line
import Hovercard from './Hovercard';
// change-line
import PosterDetailsHovercardContents from './PosterDetailsHovercardContents';
// change-line
const {useRef} = React;

...

export default function PosterByline({ poster }: Props): React.ReactElement {
  const data = useFragment(PosterBylineFragment, poster);
  // change-line
  const hoverRef = useRef(null);
  return (
    <div
      // change-line
      ref={hoverRef}
      className="byline">
      <Image image={data.profilePicture} width={60} height={60} className="byline__image" />
      <div className="byline__name">{data.name}</div>
      // change
      <Hovercard targetRef={hoverRef}>
        <PosterDetailsHovercardContents />
      </Hovercard>
      // end-change
    </div>
  );
}

You should now see that whenever you hover over someone’s name, you get a hovercard with more information. If you look inside PosterDetailsHovercardContents.tsx, you’ll find that it performs a second query with useLazyLoadQuery to fetch additional information when that component is mounted.

There’s just one problem: it always shows the same person's information, no matter which poster you hover over!


Query Variables

We need to tell the server which information we want more information about. GraphQL lets us define query variables that can be passed as arguments to specific fields. These arguments are then available on the server.

In the previous section, we saw how a field can accept arguments, but the argument values were hard-coded, e.g. url(width: 200, height: 200). With query variables, we can determine these values at runtime. They’re passed from the client to the server alongside the query itself. GraphQL variables always begin with a $ dollar sign.

Take a look inside PosterDetailsHovercardContents.tsx: you should see a query like this one:

text
const PosterDetailsHovercardContentsQuery = graphql`
  query PosterDetailsHovercardContentsQuery {
    // color1
    node(id: "1") {
      // color2
      ... on Actor @alias(as: "actor") {
        ...PosterDetailsHovercardContentsBodyFragment
      }
    }
  }
`;

The node field is a top-level field defined in our schema that lets us fetch any graph node given its unique ID. It takes the ID as an argument, which is currently hard-coded. In this exercise, we’ll be replacing this hard-coded ID with a variable supplied by our UI state.

The funny-looking ... on Actor is a type refinement. We’ll look at these in more detail in the next section and can ignore it for now. In brief, since we could supply any ID at all to the node field, there’s no way to know statically what type of node we’d be selecting. The type refinement specifies what type we expect, allowing us to use fields from the Actor type. Because the node might not actually be an Actor at runtime, we tag the refinement with @alias(as: "actor"), which exposes it as a nullable actor field on node — that's why the component below reads data.node?.actor.

Within that, we simply spread a fragment that contains the fields we want to show — about which more later. For now, here are the steps to take to replace this hard-coded ID with the ID of the poster we’re hovering over:

Step 1 — define a query variable

First we need to edit our query to declare that it accepts a query variable. Here’s the change:

text
const PosterDetailsHovercardContentsQuery = graphql`
  query PosterDetailsHovercardContentsQuery(
    // change-line
    $posterID: ID!
  ) {
    node(id: "1") {
      ... on Actor @alias(as: "actor") {
        ...PosterDetailsHovercardContentsBodyFragment
      }
    }
  }
`;
  • The variable name is $posterID. This is the symbol we’ll use within the rest of the GraphQL query to refer to the value passed in from the UI.
  • Variables have a type — in this case ID!. The ID type is a synonym for String that is used for node IDs to help distinguish them from other strings. The ! on ID! means that field is non-nullable. In GraphQL, fields are normally nullable and non-nullability is the exception.

Step 2 — pass the variable in as a field argument

Now we replace the hard-coded "1" with our new variable:

text
const PosterDetailsHovercardContentsQuery = graphql`
  query PosterDetailsHovercardContentsQuery($posterID: ID!) {
    node(
      // change-line
      id: $posterID
    ) {
    ... on Actor @alias(as: "actor") {
      ...PosterDetailsHovercardContentsBodyFragment
      }
    }
  }
`;

:::note
You can use query variables not only as field arguments, but as arguments to fragments.
:::

Step 3 — provide the argument value to useLazyLoadQuery

Now we need to pass in the actual value from our UI at runtime. The useLazyLoadQuery hook’s second argument is an object with variable values. We’ll add a new prop to our component and pass its value in there:

text
export default function PosterDetailsHovercardContents({
  // change-line
  posterID,
}: {
  // change-line
  posterID: string;
}): React.ReactElement {
  const data = useLazyLoadQuery<QueryType>(
    PosterDetailsHovercardContentsQuery,
    // change-line
    {posterID},
  );
  return <PosterDetailsHovercardContentsBody poster={data.node?.actor} />;
}

Step 4 — pass the ID in from the parent component

Now we need to supply the posterID prop from the hovercard’s parent component, which is PosterByline. Head over to that file and add id to its fragment — then pass the ID in as a prop:

text
const PosterBylineFragment = graphql`
  fragment PosterBylineFragment on Actor {
    // change-line
    id
    ...
  }
`;

export default function PosterByline({ poster }: Props): React.ReactElement {
  ...
  return (
   ...
    <PosterDetailsHovercardContents
      // change-line
      posterID={data.id}
    />
   ...
  );
}

At this point, the hovercard should show the appropriate information for each poster that we hover over.

If you use the Network inspector in your browser, you should be able to find that the variable value is being passed alongside the query:

You may also notice that this request is made only the first time you hover over a particular poster. Relay caches the results of the query and re-uses them after that, until eventually removing the cached data if it hasn’t been used recently.

Deep dive: Caching and the Relay Store

In contrast to most other systems, Relay’s caching is not based on queries, but on graph nodes. Relay maintains a local cache of all the nodes it has fetched called the Relay Store. Each node in the Store is identified and retrieved by its ID. If two queries ask for the same information, as identified by node IDs, then the second query will be fulfilled using the cached information retrieved for the first query, and not be fetched. Make sure to configure missing field handlers to take advantage of this caching behavior.

Relay will garbage-collect nodes from the Store if they aren’t “reachable” from any queries that are used, or have been recently used, by any mounted components.

Deep dive: Why GraphQL Needs a Syntax for Variables

You might be wondering why GraphQL even has the concept of variables, instead of just interpolating the value of the variables into the query string. Well, as mentioned before, the text of the GraphQL query string isn’t available at runtime, because Relay replaces it with a data structure that is more efficient. You can also configure Relay to use prepared queries, where the compiler uploads each query to the server at build time and assigns it an ID — in that case, at runtime, Relay is just telling the server “Give me query #1337”, so string interpolation isn't possible and therefore the variables have to come out of band. Even when the query string is available, passing variable values separately eliminates any issues with serializing arbitrary values and escaping strings, above what is required with any HTTP request.


Preloaded Queries

This example app is very simple, so performance isn't an issue. (In fact, the server is artificially slowed down in order to make loading states perceptible.) However, one of Relay's main concerns is to make performance as fast as possible in real apps.

Right now, the hovercard uses the useLazyLoadQuery hook, which fetches the query when the component is rendered. That means the timeline looks something like this:

Ideally, we should start the network fetch as early as possible, but here we don't start it until React is finished rendering. This timeline could look even worse if we used React.lazy to load the code for the hovercard component itself when the interaction happened. In that case, it would look like this:

Notice how we’re waiting around before we even start fetching the GraphQL query. It would be better if the query fetch began before the React component even rendered, right at the beginning in the mouse event handler itself. Then the timeline would look like this:

When the user interacts, we should immediately start fetching the query we need, while also beginning to render the component (fetching its code first if needed). Once both of these async processes are complete, we can render the component with the data available and show it to the user.

Relay provides a feature called preloaded queries that let us do this.

Let’s modify the hovercard to use preloaded queries.

Step 1 — change useLazyLoadQuery to usePreloadedQuery

As a reminder, this is the PosterDetailsHovercardContents component that currently fetches the data lazily:

text
export default function PosterDetailsHovercardContents({
  posterID,
}: {
  posterID: string;
}): React.ReactElement {
  const data = useLazyLoadQuery<QueryType>(
    PosterDetailsHovercardContentsQuery,
    {posterID},
  );
  return <PosterDetailsHovercardContentsBody poster={data.node?.actor} />;
}

It calls useLazyLoadQuery which accepts variables as its second argument. We want to change this to usePreloadedQuery. However, with preloaded queries, the variables are actually determined when the query is fetched, which will be before this component is even rendered. So instead of variables, this hook takes a query reference that contains the information it needs to retrieve the results of the query. The query reference will be created when we fetch the query in Step 2.

Change the component as follows:

text
import {usePreloadedQuery} from 'react-relay';
import type {PreloadedQuery} from 'react-relay';
import type {PosterDetailsHovercardContentsQuery as QueryType} from './__generated__/PosterDetailsHovercardContentsQuery.graphql';

export default function PosterDetailsHovercardContents({
  // change-line
  queryRef,
}: {
  // change-line
  queryRef: PreloadedQuery<QueryType>,
}): React.ReactElement {
  // change-line
  const data = usePreloadedQuery(
    PosterDetailsHovercardContentsQuery,
    // change-line
    queryRef,
  );
  ...
}

:::note
We’ll be modifying the parent component, PosterByline, to have it initiate the PosterDetailsHovercardContentsQuery query. Since it needs a reference to the query, the query needs to be exported:

text
export const PosterDetailsHovercardContentsQuery = graphql`...

:::

Step 2 — Call useQueryLoader in the parent component

Now that PosterDetailsHovercardContents expects a query ref, we need to create that query ref and pass it down from the parent component, which is PosterByline. We create the query ref using a hook called useQueryLoader. This hook also returns a function that we call in our event handler to trigger the query fetch.

text
import {useQueryLoader} from 'react-relay';
import type {PosterDetailsHovercardContentsQuery as HovercardQueryType} from './__generated__/PosterDetailsHovercardContentsQuery.graphql';
import {PosterDetailsHovercardContentsQuery} from './PosterDetailsHovercardContents';

export default function PosterByline({ poster }: Props): React.ReactElement {
  ...
  // change
  const [
    hovercardQueryRef,
    loadHovercardQuery,
  ] = useQueryLoader<HovercardQueryType>(PosterDetailsHovercardContentsQuery);
  // end-change
  return (
   ...
    <PosterDetailsHovercardContents
      // change-line
      queryRef={hovercardQueryRef}
    />
   ...
  );
}

The useQueryLoader hook returns two things we need:

  • The query ref is an opaque piece of information that usePreloadedQuery will use to retrieve the result of the query.
  • loadHovercardQuery is a function that will initiate the request.

Step 3 — Fetch the query in the event handler

Finally, we need to call loadHovercardQuery in an event handler that happens when the card is shown. Luckily the Hovercard component has a onBeginHover event that we can use:

text
export default function PosterByline({ poster }: Props): React.ReactElement {
  ...
  const [
    hovercardQueryRef,
    loadHovercardQuery,
  ] = useQueryLoader<HovercardQueryType>(PosterDetailsHovercardContentsQuery);
  // change
  function onBeginHover() {
    loadHovercardQuery({posterID: data.id});
  }
  // end-change
  return (
    <div className="byline">
      ...
      <Hovercard
        // change-line
        onBeginHover={onBeginHover}
        targetRef={hoverRef}>
        <PosterDetailsHovercardContents queryRef={hovercardQueryRef} />
      </Hovercard>
    </div>
  );
}

Note that the query variables are now passed in here where we initiate the request.

At this point, you should see the same behavior as before, but now it will be a little bit faster since Relay can get the query started earlier.

:::tip
Although we introduced queries using useLazyLoadQuery for simplicity, preloaded queries are always the preferred way to use queries in Relay because they can significantly improve performance in the real world. With the appropriate integrations with your server and router system, you can even preload the main query for a webpage on the server side before you’ve even downloaded or run any client code.
:::


Summary

  • Although all of the data initially shown on a screen should be combined into one query, user interactions needing further information can be handled with secondary queries.
  • Query variables let you pass information to the server along with your query.
  • Query variables are used by passing them into field arguments.
  • Preloaded queries are always the best way to go. For user interaction queries, initiate the fetch in the event handler. For the initial query for your screen, initiate the fetch as early as possible in your specific routing system. Use lazy-loaded queries only for quick prototyping, or not at all.

Next we'll briefly look at a way to enhance the hovercard by handling different types of posters differently. After that, we'll see how to handle situations where information that's part of the initial query also needs to be updated and refetched with different variables.


Tutorial Refetchable Fragments (`website/docs/tutorial/refetchable-fragments.mdx`)

Refetchable Fragments

In this section, we'll look at how to fetch different data in response to user input.

  • We'll build a filterable friends list.
  • We'll see how to refetch only the necessary data, not an entire query.

Since Relay encourages you to fetch all of your data in one big query, what happens when you need to refetch some data with different variables?

For example, suppose you were building a filterable list. You would need to fetch new search results when the search input changed.

One way to approach this would be to use a separate, secondary query to fetch the list, much like we did to fetch the hovercard earlier. Then we could change the query variables and refetch the query when the input changed.

However, this isn't optimal, because it needlessly uses a second query to fetch the initial list, before any user input occurs. The hovercard only appeared in response to a user interaction, but if the filterable list is visible and ready to be filtered, we might as well get its initial contents as part of our big query.

On the other hand, we don't want to refetch the entire big query whenever the input changes. Not only would this mean retrieving a large amount of data unnecessarily, it could disrupt other parts of the UI. If certain data unrelated to the filterable list has changed on the server, it would appear to randomly change when the query was refetched. Besides, this would mean threading the user input up to the top of the React tree where the query lives, which would not scale very well.

To address these issues, Relay provides refetchable fragments. These are fragments that can be refetched with new variables, separately from the rest of the query that they get spread into. They allow us to change a fragment’s arguments and fetch new data for the new argument values, just as we can fetch an entire query with new query variables.

But fragments are just that, fragments — they aren’t queries and can’t be fetched without being spread into a query and read out from the query results. So how do refetchable fragments actually work? The answer is that the Relay compiler generates a new, separate query just to refetch the fragment. The data is retrieved initially as part of whatever larger query the fragment is spread into, but then when it’s refetched, the new synthetic query is used.


To try this out, let's add a sidebar to the page with a filterable contacts list. After all, it wouldn't feel like a properly cozy newsfeed app without the ability to contact people.

We've already prepared a Sidebar component, you just need to drop it into App.tsx:

text
// change-line
import Sidebar from './Sidebar';

export default function App(): React.ReactElement {
  return (
    <RelayEnvironment>
      <React.Suspense fallback={<LoadingSpinner />}>
        <div className="app">
          <Newsfeed />
          // change-line
          <Sidebar />
        </div>
      </React.Suspense>
    </RelayEnvironment>
  );
}

You should now see a sidebar with a list of people at the top.

Have a look at ContactsList.tsx and you’ll find this fragment, which is what selects the list of contacts:

text
const ContactsListFragment = graphql`
  fragment ContactsListFragment on Viewer {
    contacts {
      id
      ...ContactRowFragment
    }
  }
`;

As it happens, the contacts field accepts a search argument that filters the list. You can try it out by changing contacts in this fragment to contacts(search: "S"). If you run npm run relay and refresh the page, you should see only those contacts that have the letter S in them.

Our goal, then, will be to hook up a search input so that, when the input changes, we refetch just this fragment with a new value for that search argument.

:::tip
As an optional exercise, try combining the queries of Sidebar and Newsfeed into a single query. There is no need for Sidebar to have its own query separate from Newsfeed; in a real app they would both have fragments and the entire screen would have only a single query. We built it with a separate query to simplify the early examples in the tutorial.
:::

Step 1 — Add a fragment argument

First we need to make this fragment accept an argument. With refetchable fragments, fragment arguments become query variables for the refetch query that Relay generates. (They also work like regular fragment arguments, so the parent query can pass in an initial value for the argument.)

text
const ContactsListFragment = graphql`
  fragment ContactsListFragment on Viewer
    // change
    @argumentDefinitions(
      search: {type: "String", defaultValue: null}
    )
    // end-change
  {
    contacts {
      id
      ...ContactRowFragment
    }
  }
`;

Step 2 — Pass the fragment argument as a field argument

Pass the fragment argument in as an argument to the contacts field.

text
const ContactsListFragment = graphql`
  fragment ContactsListFragment on Viewer
    @argumentDefinitions(
      search: {type: "String", defaultValue: null}
    )
  {
    // change-line
    contacts(search: $search) {
      id
      ...ContactRowFragment
    }
  }
`;

Remember, the first search here is the name of the argument to contacts, while the second $search is the variable created by our fragment argument.

Step 3 — Add the @refetchable directive

Next we'll add a @refetchable directive. This tells Relay to generate the extra query for refetching it. You have to specify the name of the generated query — it's a good idea to base it on the name of the fragment.

text
const ContactsListFragment = graphql`
  fragment ContactsListFragment on Viewer
    // change-line
    @refetchable(queryName: "ContactsListRefetchQuery")
    @argumentDefinitions(
      search: {type: "String", defaultValue: null}
    )
  {
     // ...
  }
`;

Step 4 — Add the search input

Now we need to actually hook this up to our UI. Take a look at the ContactsList component:

text
export default function ContactsList({ viewer }: Props) {
  const data = useFragment(ContactsListFragment, viewer);
  return (
    <Card dim={true}>
      <h3>Contacts</h3>
      {data.contacts.map(contact =>
        <ContactRow key={contact.id} contact={contact} />
      )}
    </Card>
  );
}

First we need to add a search field.

text
// change-line
import SearchInput from './SearchInput';

// change-line
const {useState} = React;

function ContactsList({viewer}) {
  const data = useFragment(ContactsListFragment, viewer);
  // change-line
  const [searchString, setSearchString] = useState('');
  // change
  const onSearchStringChanged = (value: string) => {
    setSearchString(value);
  };
  // end-change
  return (
    <Card dim={true}>
      <h3>Contacts</h3>
      // change
      <SearchInput
        value={searchString}
        onChange={onSearchStringChanged}
      />
      // end-change
      {data.contacts.map(contact =>
        <ContactRow key={contact.id} contact={contact} />
      )}
    </Card>
  );
}

Step 5 — Call useRefetchableFragment

Now to refetch the fragment when the string changes, we change useFragment to useRefetchableFragment. This hook returns a refetch function which will refetch the fragment with new variables which we provide as an argument.

text
// change-line
import {useRefetchableFragment} from 'react-relay';

function ContactsList({viewer}) {
  // change-line
  const [data, refetch] = useRefetchableFragment(ContactsListFragment, viewer);
  const [searchString, setSearchString] = useState('');
  const onSearchStringChanged = (value) => {
    setSearchString(value);
    // change-line
    refetch({search: value});
  };
  return (
    // ...
  );
}

You’ll notice that Relay gives us a callback for refetching, rather than accepting the new state variables as an argument to the hook and refetching when it is re-rendered with a different value. This means that the fetch begins as soon as the event takes place, saving some time versus waiting until React finishes re-rendering — the same principle we saw before with preloaded queries. It also gives us more control, for example if we wanted to debounce the refetch.

Step 6 — Control loading with useTransition

At this point, when the fragment is refreshed, Relay uses Suspense while the new data is loading, so the entire component is replaced with a spinner! This makes the UI fairly unusable. We would rather just keep the current data on screen until the new data is available.

The way Suspense normally works is this: When a component is missing data that it needs to render (as our component does after we refetch), it tells React to wait. When this happens, React finds the nearest Suspense component in the tree. It then replaces everything under that component with a "fallback" loading indicator.

This makes sense when initially loading a screen, but in this instance there's no reason to hide the existing UI and replace it with a spinner. While React is waiting, it can simply continue showing what's already there.

To achieve this, we can mark the refetch as a transition. Transitions are React state updates that do not need to be immediately responded to — React can wait until the data is available.

Transitions are marked by wrapping the state change in a call to a function provided by the useTransition hook. This is what the code will look like:

text
// change-line
const {useState, useTransition} = React;

function ContactsList({viewer}) {
  // change-line
  const [isPending, startTransition] = useTransition();
  const [searchString, setSearchString] = useState('');
  const [data, refetch] = useRefetchableFragment(ContactsListFragment, viewer);
  const onSearchStringChanged = (value) => {
    setSearchString(value);
    // change
    startTransition(() => {
      refetch({search: value});
    });
    // end-change
  };
  return (
    <Card dim={true}>
      <h3>Contacts</h3>
      <SearchInput
        value={searchString}
        onChange={onSearchStringChanged}
        // change-line
        isPending={isPending}
      />
      {data.contacts.map(contact =>
        <ContactRow key={contact.id} contact={contact} />
      )}
    </Card>
  );
}

While React is waiting for the new data, instead of using a Suspense fallback, React re-renders the component with the isPending flag set to true.

We simply pass the isPending flag to SearchInput (which causes it to show a spinner) while the refetch is happening. Meanwhile, by placing setSearchString outside of the transition but refetch within it, we tell React to immediately update the search input.

We should now be able to search the contacts list with a nice user experience, showing a spinner but keeping the previous data visible while loading.

Deep dive: What fragments can be refetched?

To refetch fragments, Relay has to know how to generate a query that lets it refetch just the information from the fragment. That’s only possible for fragments that meet certain requirements.

You might imagine that we could, if nothing else, re-run the original query that the fragment was spread into. However, GraphQL doesn’t guarantee that the same query will return the same results at different times. For instance, imagine you had a GraphQL field that returned the top trending posts across the site:

text
query MyQuery {
  topTrendingPosts {
    title
    summary
    date
    poster {
     ...PosterFragment
    }
  }
}

If you wanted to refresh just PosterFragment from this query, it wouldn’t work to construct a query like this:

text
query MyQuery {
  topTrendingPosts {
    poster {
     ...PosterFragment
    }
  }
}

... because the top trending post could be a different post by the time you refresh it!

Relay needs a way of identifying the specific node in the graph that the fragment ends up on, even if it can no longer be reached by the same path that the original query uses. If the node has a unique and stable ID, then we can just have a convention for querying for “the graph node with some specific ID” like so:

text
query RefetchQuery {
  node(id: "abcdef") {
    ...PosterFragment
  }
}

In fact, this is exactly the convention that Relay uses. It expects your server to implement a top-level field called node that takes an ID and gives you the graph node with that ID. (We saw node earlier with the hovercard example — there it was used to fetch a specific person given their ID using a secondary query.)

Not every graph node has a stable ID — some are ephemeral. To be used with node, your schema has to declare that its type implements an interface called Node:

text
type Person implements Node {
  id: ID!
  ...
}

The Node interface simply says it has an ID, but more importantly indicates by convention that that ID is stable and unique:

text
interface Node {
  id: ID!
}

import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

Besides fragments on types that implement Node, you can also refetch fragments that are on Viewer (since the viewer is assumed to be stable throughout a session) and that are at the top level of a query (since there’s no field above them that could change identity).

Meta only: Ents marked with GraphQLFetchable can also be refetched.


Summary

Refetchable fragments let us efficiently update specific parts of the UI in response to user input, while initializing them as part of the same query that we use for the entire screen.

Relay's pagination features are built on refetchable fragments, too. We'll explore those next.


Api Reference Entrypoint Apis Entrypoint Container (`website/docs/api-reference/entrypoint-apis/entrypoint-container.mdx`)


id: entrypoint-container
title: EntryPointContainer
slug: /api-reference/entrypoint-container/
keywords:

  • entrypoint
  • container
  • root

import DocsRating from '@site/src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

`EntryPointContainer`

For more information, see the Defining EntryPoints and Consuming EntryPoints guides.

js
function EntryPointContainer({
  entryPointReference,
  props,
}: {
  +entryPointReference: PreloadedEntryPoint<TEntryPointComponent>,
  +props: TRuntimeProps,
}): ReactElement

A React component that renders a preloaded EntryPoint.

  • entryPointReference: the value returned from a call to loadEntryPoint or acquired from the useEntryPointLoader hook.
  • props: additional runtime props that will be passed to the Component

Api Reference Entrypoint Apis Load Entrypoint (`website/docs/api-reference/entrypoint-apis/load-entrypoint.mdx`)


id: load-entrypoint
title: loadEntryPoint
slug: /api-reference/load-entrypoint/
keywords:

  • entrypoint
  • preload
  • render-as-you-fetch

import DocsRating from '@site/src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

`loadEntryPoint`

This function is designed to be used with EntryPointContainer to implement the "render-as-you-fetch" pattern.

EntryPoint references returned from loadEntryPoint will leak data to the Relay store (if they have associated queries) unless .dispose() is called on them once they are no longer referenced. As such, prefer using useEntryPointLoader when possible, which ensures that EntryPoint references are correctly disposed for you. See the useEntryPointLoader docs for a more complete example.

For more information, see the Loading EntryPoints guide.

js
const EntryPoint = require('MyComponent.entrypoint.js');

const {loadQuery} = require('react-relay');

// Generally, your component should access the environment from the React context,
// and pass that environment to this function.
const getEntrypointReference = environment => loadEntryPoint(
  { getEnvironment: () => environment },
  EntryPoint,
  {id: '4'},
);

// later: pass entryPointReference to EntryPointContainer
// Note that EntryPoint references should have .dispose() called on them,
// which is missing in this example.

Arguments

  • environmentProvider: A provider for a Relay Environment instance on which to execute the request. If you're starting this request somewhere within a React component, you probably want to use the environment you obtain from using useRelayEnvironment.
  • EntryPoint: EntryPoint to load.
  • entryPointParams: Parameters that will be passed to the EntryPoint's getPreloadProps method.

Flow Type Parameters

  • TEntryPointParams: Type parameter corresponding to the type of the first parameter of the getPreloadProps method of the EntryPoint.
  • TPreloadedQueries: the type of the queries parameter to the EntryPoint component.
  • TPreloadedEntryPoints: the type of the entrypoints parameter passed to the EntryPoint component.
  • TRuntimeProps: the type of the props prop passed to EntryPointContainer. This object is passed down to the EntryPoint component, also as props.
  • TExtraProps: if an EntryPoint's getPreloadProps method returns an object with an extraProps property, those extra props will be passed to the EntryPoint component as extraProps.
  • TEntryPointComponent: the type of the EntryPoint.
  • TEntryPoint: the type of the EntryPoint.

Return Value

An EntryPoint reference with the following properties:

  • dispose: a method that will release any query references loaded by this EntryPoint (including indirectly, by way of other EntryPoints) from being retained by the store. This can cause the data referenced by these query reference to be garbage collected.

The exact format of the return value is unstable and highly likely to change. We strongly recommend not using any other properties of the return value, as such code would be highly likely to break when upgrading to future versions of Relay. Instead, pass the result of loadEntryPoint() to EntryPointContainer.

Behavior

  • When loadEntryPoint() is called, each of an EntryPoint's associated queries (if it has any) will load their query data and query AST. Once both the query AST and the data are available, the data will be written to the store. This differs from the behavior of prepareEntryPoint_DEPRECATED, which would only write the data from an associated query to the store when that query was rendered with usePreloadedQuery.
  • The EntryPoint reference's associated query references will be retained by the Relay store, preventing the data from being garbage collected. Once you call .dispose() on the EntryPoint reference, the data from the associated queries is liable to be garbage collected.
  • loadEntryPoint may throw an error if it is called during React's render phase.

Api Reference Entrypoint Apis Use Entrypoint Loader (`website/docs/api-reference/entrypoint-apis/use-entrypoint-loader.mdx`)


id: use-entrypoint-loader
title: useEntryPointLoader
slug: /api-reference/use-entrypoint-loader/
keywords:

  • render-as-you-fetch
  • entrypoint
  • preload

import DocsRating from '@site/src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

`useEntryPointLoader`

Hook used to make it easy to safely work with EntryPoints, while avoiding data leaking into the Relay store. It will keep an EntryPoint reference in state, and dispose of it when it is no longer accessible via state.

For more information, see the Loading EntryPoints guide.

js
const {useEntryPointLoader, EntryPointContainer} = require('react-relay');

const ComponentEntryPoint = require('Component.entrypoint');

function EntryPointRevealer(): React.MixedElement {
  const environmentProvider = useMyEnvironmentProvider();
  const [
    entryPointReference,
    loadEntryPoint,
    disposeEntryPoint,
  ] = useEntryPointLoader(environmentProvider, ComponentEntryPoint);

  return (
    <>
      {
        entryPointReference == null && (
          <Button onClick={() => loadEntryPoint({})}>
            Click to reveal the contents of the EntryPoint
          </Button>
        )
      }
      {
        entryPointReference != null && (
          <>
            <Button onClick={disposeEntryPoint}>
              Click to hide and dispose the EntryPoint.
            </Button>
            <Suspense fallback="Loading...">
              <EntryPointContainer
                entryPointReference={entryPointReference}
                props={{}}
              />
            </Suspense>
          </>
        )
      }
    </>
  );
}

Arguments

  • environmentProvider: an object with a getEnvironment method that returns a Relay environment.
  • EntryPoint: the EntryPoint, usually acquired by importing a .entrypoint.js file.

Flow Type Parameters

  • TEntryPointParams: the type of the first argument to the getPreloadProps method of the EntryPoint.
  • TPreloadedQueries: the type of the queries prop passed to the EntryPoint component.
  • TPreloadedEntryPoints: the type of the entryPoints prop passed to the EntryPoint component.
  • TRuntimeProps: the type of the props prop passed to EntryPointContainer. This object is passed down to the EntryPoint component, also as props.
  • TExtraProps: if an EntryPoint's getPreloadProps method returns an object with an extraProps property, those extra props will be passed to the EntryPoint component as extraProps and have type TExtraProps.
  • TEntryPointComponent: the type of the EntryPoint component.
  • TEntryPoint: the type of the EntryPoint.

Return value

A tuple containing the following values:

  • entryPointReference: the EntryPoint reference, or null.
  • loadEntryPoint: a callback that, when executed, will load an EntryPoint, which will be accessible as entryPointReference. If a previous EntryPoint was loaded, it will dispose of it. It may throw an error if called during React's render phase.
    • Parameters
      • params: TEntryPointParams: the params passed to the EntryPoint's getPreloadProps method.
  • disposeEntryPoint: a callback that, when executed, will set entryPointReference to null and call .dispose() on it. It has type () => void. It should not be called during React's render phase.

Behavior

  • When the loadEntryPoint callback is called, each of an EntryPoint's associated queries (if it has any) will load their query data and query AST. Once both the query AST and the data are available, the data will be written to the store. This differs from the behavior of prepareEntryPoint_DEPRECATED, which would only write the data from an associated query to the store when that query was rendered with usePreloadedQuery.
  • The EntryPoint reference's associated query references will be retained by the Relay store, preventing the data from being garbage collected. Once you call .dispose() on the EntryPoint reference, the data from the associated queries is liable to be garbage collected.
  • The loadEntryPoint callback may throw an error if it is called during React's render phase.

Api Reference Graphql Graphql Directives (`website/docs/api-reference/graphql/graphql-directives.mdx`)


id: graphql-directives
title: GraphQL Directives
slug: /api-reference/graphql-and-directives/
keywords:

  • GraphQL
  • Directive
  • arguments
  • argumentDefinitions
  • connection
  • relay
  • inline
  • provider

import DocsRating from '@site/src/core/DocsRating'; import {FbInternalOnly,
OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

Relay uses directives to add additional information to GraphQL documents, which
are used by the Relay compiler to generate the
appropriate runtime artifacts. These directives only appear in your application
code and are removed from requests sent to your GraphQL server.

Note: The Relay compiler will maintain any directives supported by your server (such as @include or @skip) so they remain part of the request to the GraphQL server and won't alter generated runtime artifacts.

Note: The Relay compiler will maintain any directives supported by your server (such as @include or @skip) so they remain part of the request to the GraphQL server and won't alter generated runtime artifacts. Additional directives are documented here.

`@arguments`

@arguments is a directive used to pass arguments to a fragment that was
defined using @argumentDefinitions. For example:

graphql
query TodoListQuery($userID: ID) {
  ...TodoList_list @arguments(count: $count, userID: $userID) # Pass arguments here
}

`@argumentDefinitions`

@argumentDefinitions is a directive used to specify arguments taken by a
fragment. For example:

graphql
fragment TodoList_list on TodoList
@argumentDefinitions(
  count: {type: "Int", defaultValue: 10} # Optional argument
  userID: {type: "ID"} # Required argument
) {
  title
  todoItems(userID: $userID, first: $count) {
    # Use fragment arguments here as variables
    ...TodoItem_item
  }
}

Provided Variables

A provided variable is a special fragment variable whose value is supplied by a
specified provider function at runtime. This simplifies supplying device
attributes, user experiment flags, and other runtime constants to graphql
fragments.

To add a provided variable:

  • add an argument with provider: "[JSModule].relayprovider" to
    @argumentDefinitions
  • ensure that [JSModule].relayprovider.js exists and exports a get()
    function
    • get should return the same value on every call for a given run.
graphql
fragment TodoItem_item on TodoList
@argumentDefinitions(
  include_timestamp: {
    type: "Boolean!"
    provider: "Todo_ShouldIncludeTimestamp.relayprovider"
  }
) {
  timestamp @include(if: $include_timestamp)
  text
}
javascript
// Todo_ShouldIncludeTimestamp.relayprovider.js
export default {
  get(): boolean {
    // must always return true or false for a given run
    return check('todo_should_include_timestamp');
  },
};

Notes:

  • Even though fragments declare provided variables in argumentDefinitions,
    their parent cannot pass provided variables through @arguments.
  • An argument definition cannot specify both a provider and a defaultValue.
  • If the modified fragment is included in operations that use hack preloaders
    (@preloadable(hackPreloader: true)), you will need to manually add provided
    variables when calling RelayPreloader::gen.
    • Hack's typechecker will fail with
      The field __relay_internal__pv__[JsModule] is missing.
    • We strongly encourage switching to
      Entrypoints if possible.
  • Unstable / subject to change
    • Relay transforms provided variables to operation root variables and renames
      them to __relay_internal__pv__[JsModule].
      • Only relevant if you are debugging a query that uses provided variables.
  • Even though fragments declare provided variables in argumentDefinitions,
    their parent cannot pass provided variables through @arguments.
  • An argument definition cannot specify both a provider and a defaultValue.
  • Unstable / subject to change
    • Relay transforms provided variables to operation root variables and renames
      them to __relay_internal__pv__[JsModule].
      • Only relevant if you are debugging a query that uses provided variables.

`@catch`

@catch is a directive you can add to fields, fragments, queries, mutations,
and aliased inline fragments in your Relay queries to declare how field-level
errors are handled in runtime.

See also the @catch guide.

`@connection(key: String!, filters: [String])`

With usePaginationFragment, Relay expects connection fields to be annotated
with a @connection directive. For more detailed information and an example,
check out the
docs on usePaginationFragment.

`@refetchable(queryName: String!, directives: [String], preferFetchable: Boolean)`

With useRefetchableFragment and usePaginationFragment, Relay expects a
@refetchable directive. The @refetchable directive can only be added to
fragments that are "refetchable", that is, on fragments that are declared on
Viewer or Query types, or on a type that implements Node (i.e. a type that
has an id). The @refetchable directive will autogenerate a query with the
specified queryName. This will also generate Flow types for the query,
available to import from the generated file: <queryName>.graphql.js. For more
detailed information and examples, check out the docs on
useRefetchableFragment or
usePaginationFragment.

Optionally, you can pass in a list of directives to add to the autogenerated
query. For example, this can be used to add the @relay_test_operation
directive for testing:

[Optional] preferFetchable: Boolean

This argument tells the Relay compiler to prefer generating
fetch_MyType(): MyType queries for types that implement the Node interface.
This is useful for schemas that have adopted the @strong and @fetchable
server annotations for types. You can directly fetch concrete objects without
needing to refine Node interface to a specific type.

javascript
graphql`
  fragment FriendsListComponent_user on User
  @refetchable(
    queryName: "FriendsListFetchQuery"
    directives: ["@relay_test_operation"]
  ) {
    ...
  }
`;

`@relay(plural: Boolean)`

When defining a fragment for use with useFragment, you can use the
@relay(plural: true) directive to indicate that the hook expects the prop for
that fragment to be a list of items instead of a single item. A query or parent
that spreads a @relay(plural: true) fragment should do so within a plural
field (ie a field backed by a
GraphQL list. For
example:

javascript
// Plural fragment definition
graphql`
  fragment TodoItems_items on TodoItem @relay(plural: true) {
    id
    text
  }
`;

// Plural fragment usage: note the parent type is a list of items (`TodoItem[]`)
fragment TodoApp_app on App {
  items {
    // parent type is a list here
    ...TodoItem_items
  }
}

`@required`

@required is a directive you can add to fields in your Relay queries to
declare how null values should be handled at runtime.

See also the @required guide.

`@throwOnFieldError`

@throwOnFieldError is a directive you can add to your Relay queries and fragments to have Relay throw if any field errors are encountered when reading the query or fragment. Adding the directive will allow Relay to generate non-null types for any fields marked as @semanticNonNull in the schema.

See also the @throwOnFieldError guide.

Read more about Relay's experimental support for
Semantic Nullability.

`@semanticNonNull`

The @semanticNonNull directive can be added to fields in your schema to
indicate that the field is non-nullable in the semantic sense, but that the
client should still be prepared to handle errors.

Read more about Relay's experimental support for
Semantic Nullability.

`@alias`

@alias is a directive that allows you to give a fragment spread or inline
fragment an alias, similar to a field alias. This is useful when you want to
conditionally include a fragment and check if it was fetched, or otherwise group
data together.

For fragment spreads, the alias will default to the fragment name. For inline
fragments, the alias will default to the type name. If you wish to supply your
own name, or you have an inline fragment without any type condition, you can
specify the alias using the as argument.

graphql
fragment MyFragment on User {
  ... on User @alias(as: "myGreatAlias") {
    name
  }
}

See also the @alias guide.

`@inline`

The hooks APIs that Relay exposes allow you to read data from the store only
during the render phase. In order to read data from outside of the render phase
(or from outside of React), Relay exposes the @inline directive. The data from
a fragment annotated with @inline can be read using readInlineData.

In the example below, the function processItemData is called from a React
component. It requires an item object with a specific set of fields. All React
components that use this function should spread the processItemData_item
fragment to ensure all of the correct item data is loaded for this function.

javascript
import {graphql, readInlineData} from 'react-relay';

// non-React function called from React
function processItemData(itemRef) {
  const item = readInlineData(
    graphql`
      fragment processItemData_item on Item @inline {
        title
        price
        creator {
          name
        }
      }
    `,
    itemRef,
  );
  sendToThirdPartyApi({
    title: item.title,
    price: item.price,
    creatorName: item.creator.name,
  });
}
javascript
export default function MyComponent({item}) {
  function handleClick() {
    processItemData(item);
  }

  const data = useFragment(
    graphql`
      fragment MyComponent_item on Item {
        ...processItemData_item
        title
      }
    `,
    item,
  );

  return <button onClick={handleClick}>Process {item.title}</button>;
}

`@relay(mask: Boolean)`

It is not recommended to use @relay(mask: false). Please instead consider
using the @inline fragment.

@relay(mask: false) can be used to prevent data masking; when including a
fragment and annotating it with @relay(mask: false), its data will be
available directly to the parent instead of being masked for a different
container.

Applied to a fragment definition, @relay(mask: false) changes the generated
Flow types to be better usable when the fragment is included with the same
directive. The Flow types will no longer be exact objects and no longer contain
internal marker fields.

This may be helpful to reduce redundant fragments when dealing with nested or
recursive data within a single Component.

Keep in mind that it is typically considered an anti-pattern to create a
single fragment shared across many containers. Abusing this directive could
result in over-fetching in your application.

In the example below, the user prop will include the data for id and name
fields wherever ...Component_internUser is included, instead of Relay's normal
behavior to mask those fields:

javascript
graphql`
  fragment Component_internUser on InternUser @relay(mask: false) {
    id
    name
  }
`;

`@waterfall`

With Relay Resolvers it's
possible to create client-defined edges in the graph which point to server
types. When reading these edge fields, Relay is forced to lazily fetch the
server data for the edge. This will force Relay to make a second request to the
server to fetch the data for the edge.

To highlight this tradeoff both in the editor and during code review, the Relay
compiler expects all reads of these fields to be annotated as @waterfall.

graphql
fragment EditPost on DraftPost {
  author @waterfall {
    name
  }
}

See the Return Type
portion of the Relay Resolvers guide for more information.


Api Reference Hooks Use Lazy Load Query Extra (`website/docs/api-reference/hooks/_use-lazy-load-query-extra.mdx`)

Behavior

  • It is expected for useLazyLoadQuery to have been rendered under a RelayEnvironmentProvider, in order to access the correct Relay environment, otherwise an error will be thrown.
  • Calling useLazyLoadQuery will fetch and render the data for this query, and it may suspend while the network request is in flight, depending on the specified fetchPolicy, and whether cached data is available, or if it needs to send and wait for a network request. If useLazyLoadQuery causes the component to suspend, you'll need to make sure that there's a Suspense ancestor wrapping this component in order to show the appropriate loading state.
  • The component is automatically subscribed to updates to the query data: if the data for this query is updated anywhere in the app, the component will automatically re-render with the latest updated data.
  • After a component using useLazyLoadQuery has committed, re-rendering/updating the component will not cause the query to be fetched again.
    • If the component is re-rendered with different query variables, that will cause the query to be fetched again with the new variables, and potentially re-render with different data.
    • If the component unmounts and remounts, that will cause the current query and variables to be refetched (depending on the fetchPolicy and the state of the cache).

Differences with `QueryRenderer`

  • useLazyLoadQuery no longer takes a Relay environment as a parameter, and thus no longer sets the environment in React Context, like QueryRenderer did. Instead, useLazyLoadQuery should be used as a descendant of a RelayEnvironmentProvider, which now sets the Relay environment in Context. Usually, you should render a single RelayEnvironmentProvider at the very root of the application, to set a single Relay environment for the whole application.
  • useLazyLoadQuery will use Suspense to allow developers to render loading states using Suspense boundaries, and will throw errors if network errors occur, which can be caught and rendered with Error Boundaries. This as opposed to providing error objects or null props to the QueryRenderer render function to indicate errors or loading states.
  • useLazyLoadQuery fully supports fetch policies in order to reuse data that is cached in the Relay store instead of solely relying on the network response cache.
  • useLazyLoadQuery has better type safety guarantees for the data it returns, which was not possible with QueryRenderer since we couldn't parametrize the type of the data with a renderer api.

Api Reference Hooks Load Query (`website/docs/api-reference/hooks/load-query.mdx`)


id: load-query
title: loadQuery
slug: /api-reference/load-query/
keywords:

  • preload
  • fetch
  • query
  • render-as-you-fetch
  • retain
  • query reference

import DocsRating from '@site/src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

`loadQuery`

This function is designed to be used with the usePreloadedQuery() hook to implement the "render-as-you-fetch".

Query references returned from loadQuery will leak data into the Relay store if .dispose() is not called on them once they are no longer referenced. As such, prefer calling useQueryLoader when possible, which ensures that query references are disposed for you.

See the usePreloadedQuery docs for a more complete example.

js
const MyEnvironment = require('MyEnvironment');
const {loadQuery} = require('react-relay');

const query = graphql`
  query AppQuery($id: ID!) {
    user(id: $id) {
      name
    }
  }
`;

// Note: you should generally not call loadQuery at the top level.
// Instead, it should be called in response to an event (such a route navigation,
// click, etc.).
const queryReference = loadQuery(
  MyEnvironment,
  query,
  {id: '4'},
  {fetchPolicy: 'store-or-network'},
);

// later: pass queryReference to usePreloadedQuery()
// Note that query reference should have .dispose() called on them,
// which is missing in this example.

Arguments

  • environment: A Relay Environment instance on which to execute the request. If you're starting this request somewhere within a React component, you probably want to use the environment you obtain from using useRelayEnvironment.
  • query: GraphQL query to fetch, specified using a graphql template literal, or a preloadable concrete request, which can be acquired by requiring the file <name-of-query>$Parameters.graphql. Relay will only generate the $Parameters file if the query is annotated with @preloadable.
  • variables: Object containing the variable values to fetch the query. These variables need to match GraphQL variables declared inside the query.
  • options: [Optional] options object
    • fetchPolicy: Determines if cached data should be used, and whether to send a network request based on the cached data that is currently available in the Relay store (for more details, see our Fetch Policies and Garbage Collection guides):
      • "store-or-network": (default) will reuse locally cached data and will only send a network request if any data for the query is missing. If the query is fully cached, a network request will not be made.
      • "store-and-network": will reuse locally cached data and will always send a network request, regardless of whether any data was missing from the local cache or not.
      • "network-only": will not reuse locally cached data, and will always send a network request to fetch the query, ignoring any data that might be locally cached in Relay.
    • networkCacheConfig: [Optional] Default value: {force: true}. Object containing cache config options for the network layer. Note that the network layer may contain an additional query response cache which will reuse network responses for identical queries. If you want to bypass this cache completely (which is the default behavior), pass {force: true} as the value for this option.
  • environmentProviderOptions: [Optional] options object
    • Options passed to an environmentProvider used in prepareSurfaceEntryPoint.js.

Return Value

A query reference with the following properties:

  • dispose: a method that will release the query reference from being retained by the store. This can cause the data referenced by the query reference to be garbage collected.

The exact format of the return value is unstable and highly likely to change. We strongly recommend not using any other properties of the return value, as such code would be highly likely to break when upgrading to future versions of Relay. Instead, pass the result of loadQuery() to usePreloadedQuery().

Behavior

  • loadQuery() will fetch data if passed a query, or data and the query if passed a preloadable concrete request. Once both the query and data are available, the data from the query will be written to the store. This differs from the behavior of preloadQuery_DEPRECATED, which would only write data to the store if the query was passed to usePreloadedQuery.
  • the query reference returned from loadQuery will be retained by the Relay store, preventing the data from being garbage collected. Once you call .dispose() on the query reference, it can be garbage collected.
  • loadQuery() will throw an error if it is called during React's render phase.

Api Reference Hooks Relay Environment Provider (`website/docs/api-reference/hooks/relay-environment-provider.mdx`)


id: relay-environment-provider
title: RelayEnvironmentProvider
slug: /api-reference/relay-environment-provider/
keywords:

  • environment
  • context

import DocsRating from '@site/src/core/DocsRating';

`RelayEnvironmentProvider`

This component is used to set a Relay environment in React Context. Usually, a single instance of this component should be rendered at the very root of the application, in order to set the Relay environment for the whole application:

js
const React = require('React');
const {
  Store,
  RecordSource,
  Environment,
  Network,
  Observable,
} = require("relay-runtime");

const {RelayEnvironmentProvider} = require('react-relay');

/**
 * Custom fetch function to handle GraphQL requests for a Relay environment.
 *
 * This function is responsible for sending GraphQL requests over the network and returning
 * the response data. It can be customized to integrate with different network libraries or
 * to add authentication headers as needed.
 *
 * @param {RequestParameters} params - The GraphQL request parameters to send to the server.
 * @param {Variables} variables - Variables used in the GraphQL query.
 */
function fetchFunction(params, variables) {
  const response = fetch("http://my-graphql/api", {
    method: "POST",
    headers: [["Content-Type", "application/json"]],
    body: JSON.stringify({
      query: params.text,
      variables,
    }),
  });

  return Observable.from(response.then((data) => data.json()));
};

/**
 * Creates a new Relay environment instance for managing (fetching, storing) GraphQL data.
 */
function createEnvironment() {
  const network = Network.create(fetchFunction);
  const store = new Store(new RecordSource());
  return new Environment({ store, network });
}

const environment = createEnvironment();

function Root() {
  return (
    <RelayEnvironmentProvider environment={environment}>
      <App />
    </RelayEnvironmentProvider>
  );
}

module.exports = Root;

Props

  • environment: The Relay environment to set in React Context. Any Relay Hooks (like useLazyLoadQuery or useFragment) used in descendants of this provider component will use the Relay environment specified here

Api Reference Hooks Use Client Query (`website/docs/api-reference/hooks/use-client-query.mdx`)


id: use-client-query
title: useClientQuery
slug: /api-reference/use-client-query/
keywords:

  • query
  • read
  • client-query

import DocsRating from '@site/src/core/DocsRating';

useClientQuery hook is used to render queries that read only client fields.

The Relay Compiler fully supports client-side extensions of the schema, which allows you to define local fields and types.

graphql
# example client extension of the `Query` type
extend type Query {
  client_field: String
}

These client-only fields are not sent to the server, and should be updated
using APIs for local updates, for example commitPayload.

js
const React = require('React');

const {graphql, useClientQuery} = require('react-relay');

function ClientQueryComponent() {
  const data = useClientQuery(
    graphql`
      query ClientQueryComponentQuery {
        client_field
      }
    `,
    {}, // variables
  );

  return (
    <div>{data.client_field}</div>
  );
}

Arguments

  • query: GraphQL query specified using a graphql template literal.
  • variables: Object containing the variable values to fetch the query. These variables need to match GraphQL variables declared inside the query.

Return Value

  • data: Object that contains data which has been read out from the Relay store; the object matches the shape of specified query.
    • The Flow type for data will also match this shape, and contain types derived from the GraphQL Schema. For example, the type of data above is: {| user: ?{| name: ?string |} |}.

Behavior

  • This hooks works as useLazyLoadQuery with fetchPolicy: store-only, it does not send the network request.

Api Reference Hooks Use Fragment (`website/docs/api-reference/hooks/use-fragment.mdx`)


id: use-fragment
title: useFragment
slug: /api-reference/use-fragment/
keywords:

  • fragment
  • read
  • fragment reference

import DocsRating from '@site/src/core/DocsRating';

`useFragment`

js
import type {UserComponent_user$key} from 'UserComponent_user.graphql';

const React = require('React');

const {graphql, useFragment} = require('react-relay');

type Props = {
  user: UserComponent_user$key,
};

function UserComponent(props: Props) {
  const data = useFragment(
    graphql`
      fragment UserComponent_user on User {
        name
        profile_picture(scale: 2) {
          uri
        }
      }
    `,
    props.user,
  );

  return (
    <>
      <h1>{data.name}</h1>
      <div>
        <img src={data.profile_picture?.uri} />
      </div>
    </>
  );
}

Arguments

  • fragment: GraphQL fragment specified using a graphql template literal.
  • fragmentReference: The fragment reference is an opaque Relay object that Relay uses to read the data for the fragment from the store; more specifically, it contains information about which particular object instance the data should be read from.
    • The type of the fragment reference can be imported from the generated Flow types, from the file <fragment_name>.graphql.js, and can be used to declare the type of your Props. The name of the fragment reference type will be: <fragment_name>$key. We use our lint rule to enforce that the type of the fragment reference prop is correctly declared.

Return Value

  • data: Object that contains data which has been read out from the Relay store; the object matches the shape of specified fragment.
    • The Flow type for data will also match this shape, and contain types derived from the GraphQL Schema. For example, the type of data above is: { name: ?string, profile_picture: ?{ uri: ?string } }.

Behavior

  • The component is automatically subscribed to updates to the fragment data: if the data for this particular User is updated anywhere in the app (e.g. via fetching new data, or mutating existing data), the component will automatically re-render with the latest updated data.
  • The component will suspend if any data for that specific fragment is missing, and the data is currently being fetched by a parent query.

Api Reference Hooks Use Lazy Load Query (`website/docs/api-reference/hooks/use-lazy-load-query.mdx`)


id: use-lazy-load-query
title: useLazyLoadQuery
slug: /api-reference/use-lazy-load-query/
keywords:

  • lazy fetching
  • query
  • fetch

import DocsRating from '@site/src/core/DocsRating';

`useLazyLoadQuery`

Hook used to fetch a GraphQL query during render. This hook can trigger multiple nested or waterfalling round trips if used without caution, and waits until render to start a data fetch (when it can usually start a lot sooner than render), thereby degrading performance. Instead, prefer usePreloadedQuery.

js
const React = require('React');

const {graphql, useLazyLoadQuery} = require('react-relay');

function App() {
  const data = useLazyLoadQuery(
    graphql`
      query AppQuery($id: ID!) {
        user(id: $id) {
          name
        }
      }
    `,
    {id: 4},
    {fetchPolicy: 'store-or-network'},
  );

 return <h1>{data.user?.name}</h1>;
}

Arguments

  • gqlQuery: GraphQL query specified using a graphql template literal.
  • variables: Object containing the variable values to fetch the query. These variables need to match GraphQL variables declared inside the query.
  • options: [Optional] options object
    • fetchPolicy: [Optional] Determines if cached data should be used, and when to send a network request based on the cached data that is currently available in the Relay store (for more details, see our Fetch Policies and Garbage Collection guides):
      • "store-or-network": (default) will reuse locally cached data and will only send a network request if any data for the query is missing. If the query is fully cached, a network request will not be made.
      • "store-and-network": will reuse locally cached data and will always send a network request, regardless of whether any data was missing from the local cache or not.
      • "network-only": will not reuse locally cached data, and will always send a network request to fetch the query, ignoring any data that might be locally cached in Relay.
      • "store-only": will only reuse locally cached data, and will never send a network request to fetch the query. In this case, the responsibility of fetching the query falls to the caller, but this policy could also be used to read and operate on data that is entirely local.
    • fetchKey: [Optional] A fetchKey can be passed to force a re-evaluation of the current query and variables when the component re-renders, even if the variables didn't change, or even if the component isn't remounted (similarly to how passing a different key to a React component will cause it to remount). If the fetchKey is different from the one used in the previous render, the current query will be re-evaluated against the store, and it might be refetched depending on the current fetchPolicy and the state of the cache.
    • networkCacheConfig: [Optional] Default value: {force: true}. Object containing cache config options for the network layer. Note that the network layer may contain an additional query response cache which will reuse network responses for identical queries. If you want to bypass this cache completely (which is the default behavior), pass {force: true} as the value for this option.
    • UNSTABLE_renderPolicy: [Optional] Undocumented option.

Return Value

  • data: Object that contains data which has been read out from the Relay store; the object matches the shape of specified query.
    • The Flow type for data will also match this shape, and contain types derived from the GraphQL Schema. For example, the type of data above is: {| user: ?{| name: ?string |} |}.

import UseLazyLoadQueryExtra from './_use-lazy-load-query-extra.mdx';


Api Reference Hooks Use Mutation (`website/docs/api-reference/hooks/use-mutation.mdx`)


id: use-mutation
title: useMutation
slug: /api-reference/use-mutation/
keywords:

  • mutation

import DocsRating from '@site/src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
// @fb-only

`useMutation`

Hook used to execute a mutation in a React component.

js
import type {FeedbackLikeMutation} from 'FeedbackLikeMutation.graphql';
const React = require('React');

const {graphql, useMutation} = require('react-relay');

function LikeButton() {
  const [commit, isInFlight] = useMutation<FeedbackLikeMutation>(graphql`
    mutation FeedbackLikeMutation($input: FeedbackLikeData!) {
      feedback_like(data: $input) {
        feedback {
          id
          viewer_does_like
          like_count
        }
      }
    }
  `);

  if (isInFlight) {
    return <Spinner />;
  }

  return (
    <button
      onClick={() => {
        commit({
          variables: {
            input: {
              id: '123',
              text: 'text',
            },
          },
          onCompleted(data) {
            console.log(data);
          },
        });
      }}
    />
  );
}

Arguments

  • mutation: GraphQL mutation specified using a graphql template literal.
  • commitMutationFn: <T: MutationParameters>(IEnvironment, MutationConfig<T>): Disposable. [Optional] A function with the same signature as commitMutation, which will be called in its stead. Defaults to commitMutation.

// @fb-only

Return Value

Tuple containing the following values:

  • [0] commitMutation: The function that will execute the mutation
    • Arguments, the syntax signature is almost the same as our commitMutation API
      • variables: Object containing the variables needed for the mutation. For example, if the mutation defines an $input variable, this object should contain an input key, whose shape must match the shape of the data expected by the mutation as defined by the GraphQL schema.
      • onCompleted: Callback function executed when the request is completed and the in-memory Relay store is updated with the updater function. Takes a response object, which is the "raw" server response. Internally errors are not allowed, CRITICAL error will be thrown in the onError handler.
      • onError: Callback function executed if Relay encounters an error during the request. Internally, CRITICAL error during reading the mutation result on the server
      • optimisticResponse: Object containing the data to optimistically update the local in-memory store, i.e. immediately, before the mutation request has completed. This object must have the same shape as the mutation's response type, as defined by the GraphQL schema. If provided, Relay will use the optimisticResponse data to update the fields on the relevant records in the local data store, before optimisticUpdater is executed. If an error occurs during the mutation request, the optimistic update will be rolled back.
      • optimisticUpdater: Function used to optimistically update the local in-memory store, i.e. immediately, before the mutation request has completed. If an error occurs during the mutation request, the optimistic update will be rolled back. This function takes a store, which is a proxy of the in-memory Relay Store. In this function, the client defines how to update the local data via the store instance. For details on how to use the store, please refer to our Relay Store API Reference. Please note:
        • It is usually preferable to just pass an optimisticResponse option instead of an optimisticUpdater, unless you need to perform updates on the local records that are more complicated than just updating fields (e.g. deleting records or adding items to collections).
        • If you do decide to use an optimisticUpdater, often times it can be the same function as updater.
      • updater: Function used to update the local in-memory store based on the real server response from the mutation. If updater is not provided, by default, Relay will know to automatically update the fields on the records referenced in the mutation response; however, you should pass an updater if you need to make more complicated updates than just updating fields (e.g. deleting records or adding items to collections). When the server response comes back, Relay first reverts any changes introduced by optimisticUpdater or optimisticResponse and will then execute updater. This function takes a store, which is a proxy of the in-memory Relay Store. In this function, the client defines how to update the local data based on the server response via the store instance. For details on how to use the store, please refer to our Relay Store API
      • uploadables: An optional uploadable map, an object representing any number of uploadable items, with one key per item. Each value must be of type File or Blob.
      • No environment argument: useMutation will automatically use the current environment provided by RelayEnvironmentProvider
    • Return value:
      • disposable: Object containing a dispose function. Calling disposable.dispose() will revert the optimistic update, and Relay won't update the store or call any success/error callback, but the network request is not guaranteed to be cancelled. If the dispose is called after the mutation has succeeded, it will not rollback the update in Relay store.
  • [1] areMutationsInFlight: Will be true if any mutation triggered by calling commitMutation is still in flight. If you call commitMutation multiple times, there can be multiple mutations in flight at once.

Api Reference Hooks Use Pagination Fragment (`website/docs/api-reference/hooks/use-pagination-fragment.mdx`)


id: use-pagination-fragment
title: usePaginationFragment
slug: /api-reference/use-pagination-fragment/
keywords:

  • pagination
  • connection

import DocsRating from '@site/src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
// @fb-only
import {default as FbUsePaginationFragmentReturnValue} from '../../FbFakeContent.mdx'; // @oss-only

`usePaginationFragment`

You can use usePaginationFragment to render a fragment that uses a @connection and paginate over it:

js
import type {FriendsList_user$key} from 'FriendsList_user.graphql';

const React = require('React');

const {graphql, usePaginationFragment} = require('react-relay');

type Props = {
  user: FriendsList_user$key,
};

function FriendsList(props: Props) {
  const {
    data,
    loadNext,
    loadPrevious,
    hasNext,
    hasPrevious,
    isLoadingNext,
    isLoadingPrevious,
    refetch, // For refetching connection
  } = usePaginationFragment(
    graphql`
      fragment FriendsListComponent_user on User
      @argumentDefinitions(
        count: { type: "Int", defaultValue: 5 }
        cursor: { type: "String" }
      )
      @refetchable(queryName: "FriendsListPaginationQuery") {
        name
        friends(first: $count, after: $cursor)
        @connection(key: "FriendsList_user_friends") {
          edges {
            node {
              name
              age
            }
          }
        }
      }
    `,
    props.user,
  );

  return (
    <>
      <h1>Friends of {data.name}:</h1>

      <List items={data.friends?.edges.map(edge => edge.node)}>
        {node => {
          return (
            <div>
              {node.name} - {node.age}
            </div>
          );
        }}
      </List>
      <Button onClick={() => loadNext(10)}>Load more friends</Button>
    </>
  );
}

module.exports = FriendsList;

Arguments

  • fragment: GraphQL fragment specified using a graphql template literal.
    • This fragment must have an @connection directive on a connection field, otherwise using it will throw an error.
    • This fragment must have a @refetchable directive, otherwise using it will throw an error. The @refetchable directive can only be added to fragments that are "refetchable", that is, on fragments that are declared on Viewer or Query types, or on a type that implements Node (i.e. a type that has an id).
      • Note that you do not need to manually specify a pagination query yourself. The @refetchable directive will autogenerate a query with the specified queryName. This will also generate Flow types for the query, available to import from the generated file: <queryName>.graphql.js.
  • fragmentReference: The fragment reference is an opaque Relay object that Relay uses to read the data for the fragment from the store; more specifically, it contains information about which particular object instance the data should be read from.
    • The type of the fragment reference can be imported from the generated Flow types, from the file <fragment_name>.graphql.js, and can be used to declare the type of your Props. The name of the fragment reference type will be: <fragment_name>$key. We use our lint rule to enforce that the type of the fragment reference prop is correctly declared.

Return Value

Object containing the following properties:

  • data: Object that contains data which has been read out from the Relay store; the object matches the shape of specified fragment.
    • The Flow type for data will also match this shape, and contain types derived from the GraphQL Schema.
  • isLoadingNext: Boolean value which indicates if a pagination request for the next items in the connection is currently in flight, including any incremental data payloads.
  • isLoadingPrevious: Boolean value which indicates if a pagination request for the previous items in the connection is currently in flight, including any incremental data payloads.
  • hasNext: Boolean value which indicates if the end of the connection has been reached in the "forward" direction. It will be true if there are more items to query for available in that direction, or false otherwise.
  • hasPrevious: Boolean value which indicates if the end of the connection has been reached in the "backward" direction. It will be true if there are more items to query for available in that direction, or false otherwise.
  • loadNext: Function used to fetch more items in the connection in the "forward" direction.
    • Arguments:
      • count: Number that indicates how many items to query for in the pagination request.
      • options: [Optional] options object
        • onComplete: Function that will be called whenever the refetch request has completed, including any incremental data payloads. If an error occurs during the request, onComplete will be called with an Error object as the first parameter.
    • Return Value:
      • disposable: Object containing a dispose function. Calling disposable.dispose() will cancel the pagination request.
    • Behavior:
      • Calling loadNext will not cause the component to suspend. Instead, the isLoadingNext value will be set to true while the request is in flight, and the new items from the pagination request will be added to the connection, causing the component to re-render.
      • Pagination requests initiated from calling loadNext will always use the same variables that were originally used to fetch the connection, except pagination variables (which need to change in order to perform pagination); changing variables other than the pagination variables during pagination doesn't make sense, since that'd mean we'd be querying for a different connection.
  • loadPrevious: Function used to fetch more items in the connection in the "backward" direction.
    • Arguments:
      • count: Number that indicates how many items to query for in the pagination request.
      • options: [Optional] options object
        • onComplete: Function that will be called whenever the refetch request has completed, including any incremental data payloads. If an error occurs during the request, onComplete will be called with an Error object as the first parameter.
    • Return Value:
      • disposable: Object containing a dispose function. Calling disposable.dispose() will cancel the pagination request.
    • Behavior:
      • Calling loadPrevious will not cause the component to suspend. Instead, the isLoadingPrevious value will be set to true while the request is in flight, and the new items from the pagination request will be added to the connection, causing the component to re-render.
      • Pagination requests initiated from calling loadPrevious will always use the same variables that were originally used to fetch the connection, except pagination variables (which need to change in order to perform pagination); changing variables other than the pagination variables during pagination doesn't make sense, since that'd mean we'd be querying for a different connection.
  • refetch: Function used to refetch the connection fragment with a potentially new set of variables.
    • Arguments:
      • variables: Object containing the new set of variable values to be used to fetch the @refetchable query.
        • These variables need to match GraphQL variables referenced inside the fragment.
        • However, only the variables that are intended to change for the refetch request need to be specified; any variables referenced by the fragment that are omitted from this input will fall back to using the value specified in the original parent query. So for example, to refetch the fragment with the exact same variables as it was originally fetched, you can call refetch({}).
        • Similarly, passing an id value for the $id variable is optional, unless the fragment wants to be refetched with a different id. When refetching a @refetchable fragment, Relay will already know the id of the rendered object.
      • options: [Optional] options object
        • fetchPolicy: Determines if cached data should be used, and when to send a network request based on cached data that is available. See the Fetch Policies section for full specification.
        • onComplete: Function that will be called whenever the refetch request has completed, including any incremental data payloads.
    • Return value:
      • disposable: Object containing a dispose function. Calling disposable.dispose() will cancel the refetch request.
    • Behavior:
      • Calling refetch with a new set of variables will fetch the fragment again with the newly provided variables. Note that the variables you need to provide are only the ones referenced inside the fragment. In this example, it means fetching the translated body of the currently rendered Comment, by passing a new value to the lang variable.
      • Calling refetch will re-render your component and may cause it to suspend, depending on the specified fetchPolicy and whether cached data is available or if it needs to send and wait for a network request. If refetch causes the component to suspend, you'll need to make sure that there's a Suspense boundary wrapping this component.
      • For more details on Suspense, see our Loading States with Suspense guide.

Behavior

  • The component is automatically subscribed to updates to the fragment data: if the data for this particular User is updated anywhere in the app (e.g. via fetching new data, or mutating existing data), the component will automatically re-render with the latest updated data.
  • The component will suspend if any data for that specific fragment is missing, and the data is currently being fetched by a parent query.
  • Note that pagination (loadNext or loadPrevious), will not cause the component to suspend.

Differences with `PaginationContainer`

  • A pagination query no longer needs to be specified in this api, since it will be automatically generated by Relay by using a @refetchable fragment.
  • This api supports simultaneous bi-directional pagination out of the box.
  • This api no longer requires passing getVariables or getFragmentVariables configuration functions, like the PaginationContainer does.
    • This implies that pagination no longer has a distinction between variables and fragmentVariables, which were previously vaguely defined concepts. Pagination requests will always use the same variables that were originally used to fetch the connection, except pagination variables (which need to change in order to perform pagination); changing variables other than the pagination variables during pagination doesn't make sense, since that'd mean we'd be querying for a different connection.
  • This api no longer takes additional configuration like direction or getConnectionFromProps function (like Pagination Container does). These values will be automatically determined by Relay.
  • Refetching no longer has a distinction between variables and fragmentVariables, which were previously vaguely defined concepts. Refetching will always correctly refetch and render the fragment with the variables you provide (any variables omitted in the input will fallback to using the original values in the parent query).
  • Refetching will unequivocally update the component, which was not always true when calling refetchConnection from PaginationContainer (it would depend on what you were querying for in the refetch query and if your fragment was defined on the right object type).

Api Reference Hooks Use Prefetchable Forward Pagination Fragment (`website/docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.mdx`)


id: use-prefetchable-forward-pagination-fragment
title: usePrefetchableForwardPaginationFragment
slug: /api-reference/use-prefetchable-forward-pagination-fragment/
keywords:

  • pagination
  • connection
  • prefetching

import DocsRating from '@site/src/core/DocsRating';

NOTE: This is an experimental API and may be subject to change.
usePrefetchableForwardPaginationFragment is similar to usePaginationFragment. It adds the capability to automatically prefetch a bufferSize number of items to fill the buffer without displaying the items. And when loadNext is called, it vends from the buffer first to achieve faster pagination. It only supports forward pagination (provides APIs for loadNext, hasNext and isLoadingNext) for now.

js
import type {FriendsList_user$key} from 'FriendsList_user.graphql';

const React = require('React');

const {graphql, usePrefetchableForwardPaginationFragment} = require('react-relay');

type Props = {
  user: FriendsList_user$key,
};

function FriendsList(props: Props) {
  const {
    data,
    edges, // NOTE: It is required to use `edges` to access the items
    loadNext,
    hasNext,
    isLoadingNext,
    refetch, // For refetching connection
  } = usePrefetchableForwardPaginationFragment(
    graphql`
      fragment FriendsListComponent_user on User
      @refetchable(queryName: "FriendsListPaginationQuery") {
        name
        friends(first: $count, after: $cursor)
        @connection(key: "FriendsList_user_friends", prefetchable_pagination: true) {
          edges {
            node {
              name
              age
            }
          }
        }
      }
    `,
    props.user,
  );

  return (
    <>
      <h1>Friends of {data.name}:</h1>

      <List items={edges.map(edge => edge.node)}>
        {node => {
          return (
            <div>
              {node.name} - {node.age}
            </div>
          );
        }}
      </List>
      <Button onClick={() => loadNext(10)}>Load more friends</Button>
    </>
  );
}

module.exports = FriendsList;

Arguments

  • fragment: GraphQL fragment specified using a graphql template literal.
    • This fragment must have an @connection directive on a connection field, and set prefetchable_pagination to true, otherwise using it will throw an error.
    • This fragment must have a @refetchable directive, otherwise using it will throw an error. The @refetchable directive can only be added to fragments that are "refetchable", that is, on fragments that are declared on Viewer or Query types, or on a type that implements Node (i.e. a type that has an id).
      • Note that you do not need to manually specify a pagination query yourself. The @refetchable directive will autogenerate a query with the specified queryName. This will also generate Flow types for the query, available to import from the generated file: <queryName>.graphql.js.
  • fragmentReference: The fragment reference is an opaque Relay object that Relay uses to read the data for the fragment from the store; more specifically, it contains information about which particular object instance the data should be read from.
    • The type of the fragment reference can be imported from the generated Flow types, from the file <fragment_name>.graphql.js, and can be used to declare the type of your Props. The name of the fragment reference type will be: <fragment_name>$key. We use our lint rule to enforce that the type of the fragment reference prop is correctly declared.
  • bufferSize: The size of the buffer. The component will always try to prefetch to fill the buffer.
  • initialSize: [Optional] argument to define the initial number of items to display. If it is unset, the component shows all available items on the initial render or on refetch.
  • prefetchingLoadMoreOptions: [Optional] fetching arguments to provide to the automatic prefetch.
    • options: [Optional] options object
      • onComplete: Function that will be called whenever the request has completed, including any incremental data payloads. If an error occurs during the request, onComplete will be called with an Error object as the first parameter.
  • minimalFetchSize: Optional argument to define the minimum number of items to fetch in any requests.
  • disablePrefetching: [Optional] boolean argument to disable the automatic prefetching that fills the buffer. When true, items are only fetched in response to explicit loadNext calls. Defaults to false.
  • maxFetchSize: [Optional] argument to define the maximum number of items to fetch in any single request. Must be a positive integer. The number of items the hook computes for a request (either to fill the buffer or to fulfill a loadNext) is clamped to this value. This prevents the hook from generating large queries (for example, requesting close to the entire totalCount of a connection at once) that could overwhelm the backend. If unset (or not a positive number), no upper bound is applied.

Return Value

Object containing the following properties:

  • edges: The edges to use. This provides a filtered list of edges in the connection that excludes the buffer. Do not use the connection edges from data to render otherwise the hook will not work correctly.

  • data: Object that contains data which has been read out from the Relay store; the object matches the shape of specified fragment.

    • The Flow type for data will also match this shape, and contain types derived from the GraphQL Schema.
  • isLoadingNext: Boolean value which indicates if a pagination request for the next items in the connection is currently in flight, including any incremental data payloads. The value stays false if the hook is automatically prefetching to fill the buffer, and the code hasn't asked for more items.

  • hasNext: Boolean value which indicates if the end of the connection has been reached in the "forward" direction. It will be true if there are more items to query for available in that direction, or there are more items in the buffer, or false otherwise.

  • loadNext: Function used to fetch more items in the connection in the "forward" direction.

    • Arguments:
      • count: Number that indicates how many items to show more from buffer or fetch.
      • options: [Optional] options object
        • onComplete: Function that will be called whenever the request has completed, including any incremental data payloads. If an error occurs during the request, onComplete will be called with an Error object as the first parameter.
    • Return Value: void
    • Behavior:
      • Calling loadNext will not cause the component to suspend. The component first tries to fill the request using items prefetched in the buffer, if there aren't enough items, it sends a request. The isLoadingNext value will be set to true while the request is in flight.
      • Pagination requests initiated from calling loadNext will always use the same variables that were originally used to fetch the connection, except pagination variables (which need to change in order to perform pagination); changing variables other than the pagination variables during pagination doesn't make sense, since that'd mean we'd be querying for a different connection.
  • refetch: Function used to refetch the connection fragment with a potentially new set of variables.

    • Arguments:
      • variables: Object containing the new set of variable values to be used to fetch the @refetchable query.
        • These variables need to match GraphQL variables referenced inside the fragment.
        • However, only the variables that are intended to change for the refetch request need to be specified; any variables referenced by the fragment that are omitted from this input will fall back to using the value specified in the original parent query. So for example, to refetch the fragment with the exact same variables as it was originally fetched, you can call refetch({}).
        • Similarly, passing an id value for the $id variable is optional, unless the fragment wants to be refetched with a different id. When refetching a @refetchable fragment, Relay will already know the id of the rendered object.
      • options: [Optional] options object
        • fetchPolicy: Determines if cached data should be used, and when to send a network request based on cached data that is available. See the Fetch Policies section for full specification.
        • onComplete: Function that will be called whenever the refetch request has completed, including any incremental data payloads.
    • Return value:
      • disposable: Object containing a dispose function. Calling disposable.dispose() will cancel the refetch request.
    • Behavior:
      • Calling refetch with a new set of variables will fetch the fragment again with the newly provided variables. Note that the variables you need to provide are only the ones referenced inside the fragment. In this example, it means fetching the translated body of the currently rendered Comment, by passing a new value to the lang variable.
      • Calling refetch will re-render your component and may cause it to suspend, depending on the specified fetchPolicy and whether cached data is available or if it needs to send and wait for a network request. If refetch causes the component to suspend, you'll need to make sure that there's a Suspense boundary wrapping this component.
      • For more details on Suspense, see our Loading States with Suspense guide.

Behavior

  • The component automatically fetches for more items to fill the buffer in useEffect.
  • If a prefetch request errors, automatic prefetching is paused so the hook does not keep retrying against a failing server in a tight loop. Automatic prefetching resumes after the next explicit loadNext call or after a refetch.
  • The component is automatically subscribed to updates to the fragment data: if the data for this particular User is updated anywhere in the app (e.g. via fetching new data, or mutating existing data), the component will automatically re-render with the latest updated data.
  • The component will suspend if any data for that specific fragment is missing, and the data is currently being fetched by a parent query.
  • Note that pagination (loadNext), will not cause the component to suspend.

Api Reference Hooks Use Preloaded Query (`website/docs/api-reference/hooks/use-preloaded-query.mdx`)


id: use-preloaded-query
title: usePreloadedQuery
slug: /api-reference/use-preloaded-query/
keywords:

  • read
  • query
  • query reference

import DocsRating from '@site/src/core/DocsRating';

`usePreloadedQuery`

Hook used to access data fetched by an earlier call to loadQuery or with the help of useQueryLoader. This implements the "render-as-you-fetch" pattern:

  • Call the loadQuery callback returned from useQueryLoader. This will store a query reference in React state.
    • You can also call the imported loadQuery directly, which returns a query reference. In that case, store the item in state or in a React ref, and call dispose() on the value when you are no longer using it.
  • Then, in your render method, consume the query reference with usePreloadedQuery(). This call will suspend if the query is still pending, throw an error if it failed, and otherwise return the query results.
  • This pattern is encouraged over useLazyLoadQuery() as it can allow fetching data earlier while not blocking rendering.

For more information, see the Rendering Queries guide.

js
import type {AppQueryType} from 'AppQueryType.graphql';

const React = require('React');

const {graphql, useQueryLoader, usePreloadedQuery} = require('react-relay');

const AppQuery = graphql`
  query AppQuery($id: ID!) {
    user(id: $id) {
      name
    }
  }
`;

type Props = {
  initialQueryRef: PreloadedQuery<AppQueryType>,
};

function NameLoader(props) {
  const [queryReference, loadQuery] = useQueryLoader(
    AppQuery,
    props.initialQueryRef, /* e.g. provided by router */
  );

  return (<>
    <Button
      onClick={() => loadQuery({id: '4'})}
      disabled={queryReference != null}
    >
      Reveal your name!
    </Button>
    <Suspense fallback="Loading...">
      {queryReference != null
        ? <NameDisplay queryReference={queryReference} />
        : null
      }
    </Suspense>
  </>);
}

function NameDisplay({ queryReference }) {
  const data = usePreloadedQuery(AppQuery, queryReference);

  return <h1>{data.user?.name}</h1>;
}

Arguments

  • query: GraphQL query specified using a graphql template literal.
  • preloadedQueryReference: A PreloadedQuery query reference, which can be acquired from useQueryLoader or by calling loadQuery() .

Return Value

  • data: Object that contains data which has been read out from the Relay store; the object matches the shape of specified query.
    • The Flow type for data will also match this shape, and contain types derived from the GraphQL Schema. For example, the type of data above is: { user: ?{ name: ?string } }.

Api Reference Hooks Use Query Loader (`website/docs/api-reference/hooks/use-query-loader.mdx`)


id: use-query-loader
title: useQueryLoader
slug: /api-reference/use-query-loader/
keywords:

  • query
  • fetch
  • preload
  • render-as-you-fetch

import DocsRating from '@site/src/core/DocsRating';

`useQueryLoader`

Hook used to make it easy to safely load and retain queries. It will keep a query reference stored in state, and dispose of it when the component is disposed or it is no longer accessible via state.

This hook is designed to be used with usePreloadedQuery to implement the "render-as-you-fetch" pattern. For more information, see the Fetching Queries for Render guide.

js
import type {PreloadedQuery} from 'react-relay';

const {useQueryLoader, usePreloadedQuery} = require('react-relay');

const AppQuery = graphql`
  query AppQuery($id: ID!) {
    user(id: $id) {
      name
    }
  }
`;

function QueryFetcherExample() {
  const [
    queryReference,
    loadQuery,
    disposeQuery,
  ] = useQueryLoader(
    AppQuery,
  );

  if (queryReference == null) {
    return (
      <Button onClick={() => loadQuery({})}> Click to reveal the name </Button>
    );
  }

  return (
    <>
      <Button onClick={disposeQuery}>
        Click to hide the name and dispose the query.
      </Button>
      <React.Suspense fallback="Loading">
        <NameDisplay queryReference={queryReference} />
      </React.Suspense>
    </>
  );
}

function NameDisplay({ queryReference }) {
  const data = usePreloadedQuery(AppQuery, queryReference);

  return <h1>{data.user?.name}</h1>;
}

Arguments

  • query: GraphQL query specified using a graphql template literal.
  • initialQueryRef: [Optional] An initial PreloadedQuery to be used as the initial value of the queryReference stored in state and returned by useQueryLoader.

Return value

A tuple containing the following values:

  • queryReference: the query reference, or null.
  • loadQuery: a callback that, when executed, will load a query, which will be accessible as queryReference. If a previous query was loaded, it will dispose of it. It should not be called during React's render phase.
    • Parameters
      • variables: the variables with which the query is loaded.
      • options: LoadQueryOptions. An optional options object, containing the following keys:
        • fetchPolicy: [Optional] Determines if cached data should be used, and when to send a network request based on the cached data that is currently available in the Relay store (for more details, see our Fetch Policies and Garbage Collection guides):
          • "store-or-network": (default) will reuse locally cached data and will only send a network request if any data for the query is missing. If the query is fully cached, a network request will not be made.
          • "store-and-network": will reuse locally cached data and will always send a network request, regardless of whether any data was missing from the local cache or not.
          • "network-only": will not reuse locally cached data, and will always send a network request to fetch the query, ignoring any data that might be locally cached in Relay.
        • networkCacheConfig: [Optional] Default value: {force: true}. Object containing cache config options for the network layer. Note that the network layer may contain an additional query response cache which will reuse network responses for identical queries. If you want to bypass this cache completely (which is the default behavior), pass {force: true} as the value for this option.
  • disposeQuery: a callback that, when executed, will set queryReference to null and call .dispose() on it. It has type () => void. It should not be called during React's render phase.

Behavior

  • The loadQuery callback will fetch data if passed a query, or data and the query if passed a preloadable concrete request. Once both the query and data are available, the data from the query will be written to the store. This differs from the behavior of preloadQuery_DEPRECATED, which would only write data to the store if the query was passed to usePreloadedQuery.
  • This query reference will be retained by the Relay store, preventing the data from being garbage collected. Once .dispose() is called on the query reference, the data is liable to be garbage collected.
  • The loadQuery callback should not be called during React's render phase.

Api Reference Hooks Use Refetchable Fragment (`website/docs/api-reference/hooks/use-refetchable-fragment.mdx`)


id: use-refetchable-fragment
title: useRefetchableFragment
slug: /api-reference/use-refetchable-fragment/
keywords:

  • refetch
  • fragment

import DocsRating from '@site/src/core/DocsRating';
import {FbInternalOnly, OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
// @fb-only
// @fb-only

`useRefetchableFragment`

You can use useRefetchableFragment when you want to fetch and re-render a fragment with different data:

// @fb-only
js
import type {CommentBody_comment$key} from 'CommentBody_comment.graphql';

const React = require('React');

const {graphql, useRefetchableFragment} = require('react-relay');


type Props = {
  comment: CommentBody_comment$key,
};

function CommentBody(props: Props) {
  const [data, refetch] = useRefetchableFragment(
    graphql`
      fragment CommentBody_comment on Comment
      @refetchable(queryName: "CommentBodyRefetchQuery") {
        body(lang: $lang) {
          text
        }
      }
    `,
    props.comment,
  );

  return (
    <>
      <p>{data.body?.text}</p>
      <Button
        onClick={() => {
          refetch({lang: 'SPANISH'}, {fetchPolicy: 'store-or-network'})
        }}
      >
        Translate Comment
      </Button>
    </>
  );
}

module.exports = CommentBody;

Arguments

  • fragment: GraphQL fragment specified using a graphql template literal. This fragment must have a @refetchable directive, otherwise using it will throw an error. The @refetchable directive can only be added to fragments that are "refetchable", that is, on fragments that are declared on Viewer or Query types, or on a type that implements Node (i.e. a type that has an id and has the capability to be queried by its id field. See graphql server specification section for more details).
    • Note that you do not need to manually specify a refetch query yourself. The @refetchable directive will autogenerate a query with the specified queryName. This will also generate Flow types for the query, available to import from the generated file: <queryName>.graphql.js.
  • fragmentReference: The fragment reference is an opaque Relay object that Relay uses to read the data for the fragment from the store; more specifically, it contains information about which particular object instance the data should be read from.
    • The type of the fragment reference can be imported from the generated Flow types, from the file <fragment_name>.graphql.js, and can be used to declare the type of your Props. The name of the fragment reference type will be: <fragment_name>$key. We use our lint rule to enforce that the type of the fragment reference prop is correctly declared.

Return Value

// @fb-only

Tuple containing the following values

  • [0] data: Object that contains data which has been read out from the Relay store; the object matches the shape of specified fragment.
    • The Flow type for data will also match this shape, and contain types derived from the GraphQL Schema.
  • [1] refetch: Function used to refetch the fragment with a potentially new set of variables.
    • Arguments:
      • variables: Object containing the new set of variable values to be used to fetch the @refetchable query.
        • These variables need to match GraphQL variables referenced inside the fragment.
        • If the fragment key passed to useRefetchableFragment is optional then all non-optional variables must be passed including, potentially, the object's ID since Relay may not have any existing variables to reuse.
        • If the fragment key is non-optional, only the variables that are intended to change for the refetch request need to be specified; any variables referenced by the fragment that are omitted from this input will fall back to using the value specified in the original parent query. So for example, to refetch the fragment with the exact same variables as it was originally fetched, you can call refetch({}).
        • Similarly, if the fragment key is non-optional, passing an id value for the $id variable is optional, unless the fragment wants to be refetched with a different id. When refetching a non-nullable @refetchable fragment, Relay will already know the id of the rendered object.
      • options: [Optional] options object
        • fetchPolicy: Determines if cached data should be used, and when to send a network request based on cached data that is available. See the Fetch Policies section for full specification.
        • onComplete: Function that will be called whenever the refetch request has completed, including any incremental data payloads.
    • Return value:
      • disposable: Object containing a dispose function. Calling disposable.dispose() will cancel the refetch request.
    • Behavior:
      • Calling refetch with a new set of variables will fetch the fragment again with the newly provided variables. Note that the variables you need to provide are only the ones referenced inside the fragment. In this example, it means fetching the translated body of the currently rendered Comment, by passing a new value to the lang variable.
      • Calling refetch will re-render your component and may cause it to suspend, depending on the specified fetchPolicy and whether cached data is available or if it needs to send and wait for a network request. If refetch causes the component to suspend, you'll need to make sure that there's a Suspense boundary wrapping this component.
      • For more details on Suspense, see our Loading States with Suspense guide.

Behavior

  • The component is automatically subscribed to updates to the fragment data: if the data for this particular User is updated anywhere in the app (e.g. via fetching new data, or mutating existing data), the component will automatically re-render with the latest updated data.
  • The component will suspend if any data for that specific fragment is missing, and the data is currently being fetched by a parent query.

Differences with `RefetchContainer`

  • A refetch query no longer needs to be specified in this api, since it will be automatically generated by Relay by using a @refetchable fragment.
  • Refetching no longer has a distinction between refetchVariables and renderVariables, which were previously vaguely defined concepts. Refetching will always correctly refetch and render the fragment with the variables you provide (any variables omitted in the input will fallback to using the original values from the parent query).
  • Refetching will unequivocally update the component, which was not always true when calling refetch from RefetchContainer (it would depend on what you were querying for in the refetch query and if your fragment was defined on the right object type).

Api Reference Hooks Use Relay Environment (`website/docs/api-reference/hooks/use-relay-environment.mdx`)


id: use-relay-environment
title: useRelayEnvironment
slug: /api-reference/use-relay-environment/
keywords:

  • environment
  • context

import DocsRating from '@site/src/core/DocsRating';

`useRelayEnvironment`

Hook used to access a Relay environment that was set by a RelayEnvironmentProvider:

js
const React = require('React');

const {useRelayEnvironment} = require('react-relay');

function MyComponent() {
  const environment = useRelayEnvironment();

  const handler = useCallback(() => {
    // For example, can be used to pass the environment to functions
    // that require a Relay environment.
    commitMutation(environment, ...);
  }, [environment])

  return (...);
}

module.exports = MyComponent;

Api Reference Hooks Use Subscription (`website/docs/api-reference/hooks/use-subscription.mdx`)


id: use-subscription
title: useSubscription
slug: /api-reference/use-subscription/
keywords:

  • subscription

import DocsRating from '@site/src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
import GraphQLSubscriptionConfig from '../types/GraphQLSubscriptionConfig.mdx';

`useSubscription`

Hook used to subscribe and unsubscribe to a subscription.

js
import {graphql, useSubscription} from 'react-relay';
import {useMemo} from 'react';

const subscription = graphql`
  subscription UserDataSubscription($input: InputData!) {
    # ...
  }
`;

function UserComponent({ id }) {
  // IMPORTANT: your config should be memoized.
  // Otherwise, useSubscription will re-render too frequently.
  const config = useMemo(() => ({
    variables: {id},
    subscription,
  }), [id, subscription]);

  useSubscription(config);

  return (/* ... */);
}

Arguments

  • config: a memoized config of type GraphQLSubscriptionConfig passed to requestSubscription
  • requestSubscriptionFn: ?<TSubscriptionPayload>(IEnvironment, GraphQLSubscriptionConfig<TSubscriptionPayload>) => Disposable. An optional function with the same signature as requestSubscription, which will be called in its stead. Defaults to requestSubscription.

Behavior

  • This is only a thin wrapper around the requestSubscription API. It will:
    • Subscribe when the component is mounted with the given config
    • Unsubscribe when the component is unmounted
    • Unsubscribe and resubscribe with new values if the environment, config or requestSubscriptionFn changes.
  • If you have the need to do something more complicated, such as imperatively requesting a subscription, please use the requestSubscription API directly.
  • See the GraphQL Subscriptions Guide for a more detailed explanation of how to work with subscriptions.

:::note
useSubscription doesn't automatically add client_subscription_id. You may need to provide an arbitrary client_subscription_id to config.variables.input
:::


Api Reference Relay Resolvers Docblock Format (`website/docs/api-reference/relay-resolvers/docblock-format.mdx`)


id: docblock-format title: 'Docblock Format' slug: /api-reference/relay-resolvers/docblock-format/

import {FbInternalOnly, fbContent} from 'docusaurus-plugin-internaldocs-fb/internal';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Relay Resolvers allow you to define additional types and fields in your GraphQL schema that are backed by client-side data. To achieve this, the Relay compiler looks for special @relayType and @relayField docblocks in your code. These docblocks define the types and fields in your schema and also tell Relay where to find the resolver functions that implement them.

For an overview of Relay Resolvers and how to think about them, see the Relay Resolvers guide. This page documents the different docblock tags that the Relay compiler looks for, and how to use them.

:::note The Relay compiler only looks at docblocks which include the
@relayType or @relayField tag. Any other docblocks will be ignored.
:::

`@relayType TypeName`

The @relayType tag followed by a single name defines a new GraphQL type in your schema. By default it is expected to be followed by an exported function whose name matches the type name. The function should accept an ID as its sole argument and return the JavaScript model/object which is the backing data for the type. See @weak for an alternative way to define the backing data for a type.

<Tabs
groupId="resolver"
defaultValue="Docblock"
values={fbContent({
internal: [
{label: 'Docblock', value: 'Docblock'},
{label: 'Flow', value: 'Flow'},
],
external: [
{label: 'Docblock', value: 'Docblock'},
]
})}>

tsx
/**
 * @relayType User
 */
export function User(id): UserModel {
  return UserModel.getById(id);
}
tsx
/**
 * @relayType
 */
export function User(id): UserModel {
  return UserModel.getById(id);
}

See the Defining Types guide for more information.

`@relayField TypeName.fieldName: FieldTypeName`

If the typename in a @relayField tag is followed by a dot and then a field definition, it defines a new field on the type. The portion following the . is expected to follow GraphQL's
schema definition language.

Field definitions are expected to be followed by an exported function whose name matches the field name. The function should accept the model/object returned by the type resolver as its sole argument and return the value of the field.

<Tabs
groupId="resolver"
defaultValue="Docblock"
values={fbContent({
internal: [
{label: 'Docblock', value: 'Docblock'},
{label: 'Flow', value: 'Flow'},
],
external: [
{label: 'Docblock', value: 'Docblock'},
]
})}>

tsx
/**
 * @relayField User.name: String
 */
export function name(user: UserModel): string {
  return user.name;
}
tsx
/**
 * @relayType
 */
export function name(user: UserModel): string {
  return user.name;
}

See the Defining Fields guide for more information.

`@rootFragment`

Relay Resolvers may also be used to model data that is derived from other data in the graph. These fields will be automatically recomputed by Relay when the data they depend on changes.

To define a derived field, use the @rootFragment tag on an existing field
definition, and follow it with the name of a fragment that defines the data that the field depends on. The resolver function for the field will be passed a fragment key which can be used to read the fragment data using readFragment().

<Tabs
groupId="resolver"
defaultValue="Docblock"
values={fbContent({
internal: [
{label: 'Docblock', value: 'Docblock'},
{label: 'Flow', value: 'Flow'},
],
external: [
{label: 'Docblock', value: 'Docblock'},
]
})}>

tsx
import {readFragment} from 'relay-runtime';

/**
 * @relayField User.fullName: String
 * @rootFragment UserFullNameFragment
 */
export function fullName(key: UserFullNameFragment$key): string {
  const user = readFragment(
    graphql`
      fragment UserFullNameFragment on User {
        firstName
        lastName
      }
    `,
    key,
  );
  return `${user.firstName} ${user.lastName}`;
}
tsx
import {readFragment} from 'relay-runtime';

/**
 * @relayType
 */
export function fullName(key: UserFullNameFragment$key): string {
  const user = readFragment(
    graphql`
      fragment UserFullNameFragment on User {
        firstName
        lastName
      }
    `,
    key,
  );
  return `${user.firstName} ${user.lastName}`;
}

See Derived Fields for more information.

`@live`

When modeling client state that can change over time, a resolver function which returns a single value is not sufficient. To accommodate this, Relay Resolvers allow you to define a field that returns a stream of values over time. This is done by adding the @live tag to a field or type definition.

@live resolvers must return an object with the shape of a LiveStateValue to allow Relay to read the current value and subscribe to changes.

<Tabs
groupId="resolver"
defaultValue="Docblock"
values={fbContent({
internal: [
{label: 'Docblock', value: 'Docblock'},
{label: 'Flow', value: 'Flow'},
],
external: [
{label: 'Docblock', value: 'Docblock'},
]
})}>

tsx
import type {LiveState} from 'relay-runtime';

/**
* @relayField Query.counter: Int
* @live
*/
export function counter(): LiveState<number> {
  return {
    read: () => store.getState().counter,
    subscribe: cb => {
      return store.subscribe(cb);
    },
  };
}
tsx
import type {LiveState} from 'relay-runtime';

/**
* @relayType
*/
export function counter(): LiveState<number> {
  return {
    read: () => store.getState().counter,
    subscribe: cb => {
      return store.subscribe(cb);
    },
  };
}

See the Live Fields guide for
more information.

`@weak`

By default, Relay Resolvers expect the backing data for a type to be returned by a resolver function. However, in some cases objects of a given type may not have identifiers. In this case, you can use the @relayType TypeName syntax described above followed by the tag @weak to define a "weak" type.

Weak type declarations are expected to be followed by an exported type
definition whose name matches the type name.

<Tabs
groupId="resolver"
defaultValue="Docblock"
values={fbContent({
internal: [
{label: 'Docblock', value: 'Docblock'},
{label: 'Flow', value: 'Flow'},
],
external: [
{label: 'Docblock', value: 'Docblock'},
]
})}>

tsx
/**
* @relayType ProfilePicture
* @weak
*/
export type ProfilePicture = {
  url: string;
  width: number;
  height: number;
};
tsx
/**
* @relayType
*/
export type ProfilePicture = {
  url: string;
  width: number;
  height: number;
};

See the [Weak Types](../../guides/relay-resolvers/defining-types.mdx#Defining a “weak” type) guide for more information including how to define an edge to a weak type.

`@deprecated`

Just like the GraphQL schema definition language, Relay Resolvers support the @deprecated tag to mark a field as deprecated. The tag may be followed by a string which will be used as the deprecation reason. Deprecated fields will
receive special treatment in the editor if you are using the
Relay VSCode extension.

tsx
/**
 * @relayField User.name: String
 * @deprecated Use `fullName` instead.
 */
export function name(user: UserModel): string {
  return user.name;
}

See the Deprecated guide for more information.

Descriptions

Any free text in the docblock (text not following a tag) will be used as the description for the type or field. This description will be surfaced in the editor if you are using the Relay VSCode extension.

tsx
/**
 * @relayField User.name: String
 *
 * What's in a name? That which we call a rose by any other name would smell
 * just as sweet.
 */
export function name(user: UserModel): string {
  return user.name;
}

See the Descriptions guide for more information.


Api Reference Relay Resolvers Runtime Functions (`website/docs/api-reference/relay-resolvers/runtime-functions.mdx`)


id: runtime-functions title: "Runtime Functions" slug: /api-reference/relay-resolvers/runtime-functions/

This page documents the runtime functions associated with Relay Resolvers. For an overview of Relay Resolvers and how to think about them, see the Relay Resolvers guide.

RelayModernStore

RelayModernStore exposes batchLiveStateUpdates(). See Live Fields for more details of how to use this method.

`readFragment()`

Derived resolver fields model data that is derived from other data in the graph. To read the data that a derived field depends on, they must use the readFragment() function which is exported from relay-runtime. This function accepts a GraphQL fragment and a fragment key, and returns the data for the fragment.

:::warning
readFragment() may only be used in Relay Resolvers. It will throw an error if used in any other context.
:::

tsx
import {readFragment} from "relay-runtime";

/**
 * @relayField User.fullName: String
 * @rootFragment UserFullNameFragment
 */
export function fullName(key: UserFullNameFragment$key): string {
  const user = readFragment(graphql`
    fragment UserFullNameFragment on User {
      firstName
      lastName
    }
  `, key);
  return `${user.firstName} ${user.lastName}`;
}

Note that Relay will ensure your field resolver is recomputed any time data in that fragment changes.

See the Derived Fields guide for more information.

`suspenseSentinel()`

Live resolvers model client state that can change over time. If at some point during that field's lifecycle, the data being read is in a pending state, for example if the data is being fetched from an API, the resolver may return the suspenseSentinel() to indicate that the data is not yet available.

Relay expects that when the data is available, the LiveStateValue will notify Relay by calling the subscribe callback.

tsx
import {suspenseSentinel} from 'relay-runtime';

/**
 * @relayField Query.myIp: String
 * @live
 */
export function myIp(): LiveState<string> {
  return {
    read: () => {
      const state = store.getState();
      const ipLoadObject = state.ip;
      if (ipLoadObject.status === "LOADING") {
        return suspenseSentinel();
      }
      return state.ip;
    },
    subscribe: (callback) => {
      return store.subscribe(callback);
    },
  };
}

See the Live Fields guide for more information.

`useClientQuery()`

If a query contains only client fields, it may not currently be used with hooks like usePreloadedQuery and useLazyLoadQuery since both of those hooks assume they will need to issue a network request. If you attempt to use these APIs in Flow you will get a type error.

Instead, for client-only queries, you can use the useClientQuery hook:

tsx
import {useClientQuery} from 'react-relay';

export function MyComponent() {
  const data = useClientQuery(graphql`
    query MyQuery {
      myIp
    }
  `);
  return <div>{data.myIp}</div>;
}

Api Reference Relay Runtime Commit Mutation (`website/docs/api-reference/relay-runtime/commit-mutation.mdx`)


id: commit-mutation
title: commitMutation
slug: /api-reference/commit-mutation/
keywords:

  • mutation

import DocsRating from '@site/src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
import MutationConfig from '../types/MutationConfig.mdx';
import Disposable from '../types/Disposable.mdx';

`commitMutation`

Imperatively execute a mutation.

See also the useMutation API and Guide to Updating Data.

js
import type {FeedbackLikeMutation} from 'FeedbackLikeMutation.graphql';
const React = require('React');

const {graphql, commitMutation} = require('react-relay');

function likeFeedback(environment: IEnvironment): Disposable {
  return commitMutation<FeedbackLikeMutation>(environment, {
    mutation: graphql`
      mutation FeedbackLikeMutation($input: FeedbackLikeData!) {
        feedback_like(data: $input) {
          feedback {
            id
            viewer_does_like
            like_count
          }
        }
      }
    `,
    variables: {
      input: {
        id: '123',
      },
    },
  });
}

Arguments

Return Value

  • A Disposable which:
    • If called before the request completes, will cancel and revert any optimistic updates and prevent the onComplete and onError callbacks from being executed. It will not necessarily cancel any network request. Will cause the onUnsubscribe callback to be called.
    • If called after the initial request completes, will do nothing.

Api Reference Relay Runtime Fetch Query (`website/docs/api-reference/relay-runtime/fetch-query.mdx`)


id: fetch-query
title: fetchQuery
slug: /api-reference/fetch-query/
keywords:

  • observable
  • query
  • fetch

import DocsRating from '@site/src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';

`fetchQuery`

If you want to fetch a query outside of React, you can use the fetchQuery function from react-relay:

js
// You should prefer passing an environment that was returned from useRelayEnvironment()
const MyEnvironment = require('MyEnvironment');
const {fetchQuery} = require('react-relay');

fetchQuery(
  environment,
  graphql`
    query AppQuery($id: ID!) {
      user(id: $id) {
        name
      }
    }
  `,
  {id: 4},
)
.subscribe({
  start: () => {...},
  complete: () => {...},
  error: (error) => {...},
  next: (data) => {...}
});

Arguments

  • environment: A Relay Environment instance to execute the request on. If you're starting this request somewhere within a React component, you probably want to use the environment you obtain from using useRelayEnvironment.
  • query: GraphQL query to fetch, specified using a graphql template literal.
  • variables: Object containing the variable values to fetch the query. These variables need to match GraphQL variables declared inside the query.
  • options: [Optional] options object
    • networkCacheConfig: *_[Optional]_ *Object containing cache config options

      • force: Boolean value. If true, will bypass the network response cache. Defaults to true.
    • fetchPolicy: [Optional] Determines whether fetchQuery should reuse data that is already available in the local Relay store. Must be one of the following values:

      • "network-only": (default) Always issue a network request, regardless of what is in the store.
      • "store-or-network": Reuse data from the store if the full query is available locally; otherwise, issue a network request.

      The "store-and-network" and "store-only" fetch policies are not supported by fetchQuery, because it is designed around issuing a single network request and returning the response.

Flow Type Parameters

  • TQuery: Type parameter that should correspond to the Flow type for the specified query. This type is available to import from the the auto-generated file: <query_name>.graphql.js. It will ensure that the type of the data provided by the observable matches the shape of the query, and enforces that the variables passed as input to fetchQuery match the type of the variables expected by the query.

Return Value

  • observable: Returns an observable instance. To start the request, subscribe or toPromise must be called on the observable. Exposes the following methods:
    • subscribe: Function that can be called to subscribe to the observable for the network request. Keep in mind that this subscribes you only to the fetching of the query, not to any subsequent changes to the data within the Relay Store.
      • Arguments:
        • observer: Object that specifies observer functions for different events occurring on the network request observable. May specify the following event handlers as keys in the observer object:
          • start: Function that will be called when the network requests starts. It will receive a single subscription argument, which represents the subscription on the network observable.
          • complete: Function that will be called if and when the network request completes successfully.
          • next: Function that will be called every time a payload is received from the network. It will receive a single data argument, which represents a snapshot of the query data read from the Relay store at the moment a payload was received from the server.
          • error: Function that will be called if an error occurs during the network request. It will receive a single error argument, containing the error that occurred.
          • unsubscribe: Function that will be called whenever the subscription is unsubscribed. It will receive a single subscription argument, which represents the subscription on the network observable.
      • Return Value:
        • subscription: Object representing a subscription to the observable. Calling subscription.unsubscribe() will cancel the network request.
    • toPromise:
      • Return Value:
        • promise: Returns a promise that will resolve when the first network response is received from the server. If the request fails, the promise will reject. Cannot be cancelled.

:::info
The next function may be called multiple times when using Relay's Incremental Data Delivery capabilities to receive multiple payloads from the server.
:::

Behavior

  • fetchQuery will automatically save the fetched data to the in-memory Relay store, and notify any components subscribed to the relevant data.
  • fetchQuery will NOT retain the data for the query, meaning that it is not guaranteed that the data will remain saved in the Relay store at any point after the request completes. If you wish to make sure that the data is retained outside of the scope of the request, you need to call environment.retain() directly on the query to ensure it doesn't get deleted. See our section on Controlling Relay's GC Policy for more details.
  • fetchQuery will automatically de-dupe identical network requests (same query and variables) that are in flight at the same time, and that were initiated with fetchQuery.

Behavior with `.toPromise()`

If desired, you can convert the request into a Promise using **.toPromise()**. Note that toPromise will start the query and return a Promise that will resolve when the first piece of data returns from the server and cancel further processing. That means any deferred or 3D data in the query may not be processed. We generally recommend against using toPromise() for this reason.

js
const {fetchQuery} = require('react-relay');

fetchQuery(
  environment,
  graphql`
    query AppQuery($id: ID!) {
      user(id: $id) {
        name
      }
    }
  `,
  {id: 4},
)
.toPromise() // NOTE: don't use, this can cause data to be missing!
.then(data => {...})
.catch(error => {...};
  • toPromise Returns a promise that will resolve when the first network response is received from the server. If the request fails, the promise will reject. Cannot be cancelled.

Api Reference Relay Runtime Field Logger (`website/docs/api-reference/relay-runtime/field-logger.mdx`)


id: field-logger
title: relayFieldLogger
slug: /api-reference/field-logger/
keywords:

  • environment
  • logging
  • error

Relay includes a number of features that allow for granular handling of field errors:

  • @required provides declarative handling of field nullability
  • @catch allows you to explicitly handle field errors
  • @throwOnFieldError lets you treat field errors as exceptions
  • Relay Resolvers coerce thrown exceptions to null, matching the GraphQL spec

In each of these cases, field errors are handled by Relay. However, it can still be important to track that these errors are occurring in your app, and monitor or resolve them. To enable this, the Relay Environment can be configured with a relayFieldLogger. This logger is a function which is called with events each time Relay handles a field-level error.

Providing a field logger looks something like this:

ts
import {Environment} from "relay-runtime";

const environment = new Environment({
    relayFieldLogger: (event) => {
        switch(event.kind) {
            case "missing_expected_data.log":
                // ...
                break;
            // ... handle other events
        }
    },
    network: // ...
    store: // ...
});

Event Types

The Field Logger currently can receive the following events:

Missing Expected Data Log

Data which Relay expected to be in the store (because it was requested by the parent query/mutation/subscription) was missing. This can happen due to graph relationship changes observed by other queries/mutations, or imperative updates that don't provide all needed data.

See Graph Relationship Changes.

In this case Relay will render with the referenced field as undefined.

:::note
This may break with the type contract of Relay's generated types.
:::

To turn this into a hard error for a given fragment/query, you can use @throwOnFieldError

ts
export type MissingExpectedDataLogEvent = {
  +kind: 'missing_expected_data.log',
  +owner: string,
  +fieldPath: string,
};

Missing Expected Data Throw

Data which Relay expected to be in the store (because it was requested by the parent query/mutation/subscription) was missing. This can happen due to graph relationship changes observed by other queries/mutations, or imperative updates that don't provide all needed data.

See Graph Relationship Changes.

This event is called .throw because the missing data was encountered in a
query/fragment/mutation with @throwOnFieldError

Relay will throw immediately after logging this event. If you wish to
customize the error being thrown, you may throw your own error.

:::note
Only throw on this event if handled is false. Errors that have been handled by a @catch directive or by making a resolver null will have handled: true and should not trigger a throw.
:::

ts
export type MissingExpectedDataThrowEvent = {
  +kind: 'missing_expected_data.throw',
  +owner: string,
  +fieldPath: string,
  +handled: boolean,
};

The fieldPath property is a . separated string containing the path from the query/mutation/fragment root to the field which was missing. Note that there are some cases where we are missing data that is not strictly a field. In those cases the path will end with one of these segments:

  1. path.to.<record> An edge points to an ID that is not present in the Relay store
  2. path.to.<abstract-type-hint> A virtual field inserted by the Relay compiler to detect if a type condition holds is missing from the Relay store

Missing Required Field Log

A field was marked as @required(action: LOG) but was null or missing in the store.

ts
export type MissingRequiredFieldLogEvent = {
  +kind: 'missing_required_field.log',
  +owner: string,
  +fieldPath: string,
};

Missing Required Field Throw

A field was marked as @required(action: THROW) but was null or missing in the store.

Relay will throw immediately after logging this event. If you wish to customize the error being thrown, you may throw your own error.

:::note
Only throw on this event if handled is false. Errors that have been
handled by a @catch directive or by making a resolver null will have
handled: true and should not trigger a throw.
:::

ts
export type MissingRequiredFieldThrowEvent = {
  +kind: 'missing_required_field.throw',
  +owner: string,
  +fieldPath: string,
  +handled: boolean,
};

Relay Resolver Error

A Relay Resolver that is currently being read threw a JavaScript error when it was last evaluated. By default, the value has been coerced to null and passed to the product code.

If @throwOnFieldError was used on the parent query/fragment/mutation, you will also receive a runtime exception when the field is read.

:::note
Only throw on this event if handled is false. Errors that have been handled by a @catch directive or by making a resolver null will have handled: true and should not trigger a throw.

ts
export type RelayResolverErrorEvent = {
  +kind: 'relay_resolver.error',
  +owner: string,
  +fieldPath: string,
  +error: Error,
  +shouldThrow: boolean,
  +handled: boolean,
};

GraphQL Payload Field Error

A field being read by Relay was marked as being in an error state by the GraphQL response

If the field's parent query/fragment/mutation was annotated with @throwOnFieldError and no @catch directive was used to catch the error, Relay will throw an error immediately after logging this event.

:::note
Only throw on this event if handled is false. Errors that have been handled by a @catch directive or by making a resolver null will have handled: true and should not trigger a throw.
:::

ts
export type RelayFieldPayloadErrorEvent = {
  +kind: 'relay_field_payload.error',
  +owner: string,
  +fieldPath: string,
  +error: TRelayFieldError,
  +shouldThrow: boolean,
  +handled: boolean,
};

Api Reference Relay Runtime Observe Fragment (`website/docs/api-reference/relay-runtime/observe-fragment.mdx`)


id: observe-fragment
title: observeFragment
slug: api-reference/observe-fragment
keywords:

  • observable
  • fragment

import DocsRating from '@site/src/core/DocsRating';

:::warning
observeFragment is still an experimental API. It currently has some limitations and may evolve slightly during this phase.
:::

`observeFragment`

In some cases it can be useful to define data that you wish to read using a GraphQL fragment, but then consume it outside of React render function. observeFragment allows you to consume the state and value of a fragment as it changes over time. This includes loading and error states as well as changes to the data as it gets updated by local updates, mutations or updates to Relay's normalized store from other queries.

To read a fragment's data just once, see waitForFragmentData.

:::caution
When using observeFragment with a plural fragment, the current implementation notifies the subscription multiple times if a store update impacting multiple list items gets published. Since the notifications happen synchronously, it is advised to debounce for a tick and only use the last payload for batching.
:::

Example

ts
import {observeFragment} from "relay-runtime/experimental";
import { useEffect } from "react";
import { useFragment } from "react-relay";
import { graphql } from "relay-runtime";

function MyComponent({ key }) {
  const user = useFragment(
    graphql`
      fragment UserFragment on User {
        ...TitleFragment
      }
    `,
    key,
  );

  // Update the title as the user's name changes without triggering rerenders.
  useEffect(() => {
    const subscription = observeFragment(
      graphql`
        fragment TitleFragment on User {
          name
        }
      `,
      user,
    ).subscribe({
      next: (result) => {
        switch(result.kind) {
          case "loading":
            window.title = "...loading";
            break;
          case "error":
            window.title = "Oops, we hit an error";
            break;
          case "ok":
            window.title = `Welcome ${result.value.name}`;
            break;
        }
      }
    });
    return () => {
      subscription.unsubscribe();
    };
  }, [user]);

  return <div>Check out the document title!</div>;
}

Arguments

  • environment: IEnvironment. A Relay environment.
  • fragment: GraphQL fragment specified using a graphql template literal.
  • fragmentReference: The fragment reference is an opaque Relay object that Relay uses to read the data for the fragment from the store; more specifically, it contains information about which particular object instance the data should be read from.
    • The type of the fragment reference can be imported from the generated Flow types, from the file <fragment_name>.graphql.js, and can be used to declare the type of your Props. The name of the fragment reference type will be: <fragment_name>$key. We use our lint rule to enforce that the type of the fragment reference prop is correctly declared.

Return Value

  • An Observable which returns a discriminated union modeling the three possible states in which a fragment's data might be:
    • {state: 'ok', value: T} - When data is available the state is 'ok'. T is the data defined in the fragment.
    • {state: 'error': error: Error} - When the fragment is in an error state either due to network level errors, @throwOnFieldError or @required(action: THROW) field errors.
    • {state: 'loading'} - When the parent request, or current @defer payload is still in flight, or a @live Relay Resolver being read is in a suspended state.

Api Reference Relay Runtime Relay Environment (`website/docs/api-reference/relay-runtime/relay-environment.mdx`)


id: relay-environment title: Relay Environment slug: /api-reference/relay-runtime/relay-environment

The core of Relay's