facebook / facebook/relay

Proposed project: @alias

Open
#4,437 0 comments 11 reactions 0 assignees View on GitHub
project
Dominant language
Rust
Stars
19k
Forks
1.9k
PR merge metrics
No merged PRs in 30d

Description

Some time ago, we did a hack-week where we explored the ability to materialize fragment spreads (and their associated data or fragment keys) as concrete properties on the objects Relay returns. The core of this feature has been implemented in Relay since then, but the auxiliary work to bring it to production has not been done. I _thought_ I had shared this in open source previously, but I can't find it anywhere, so I figured I'd open an issue to share the idea here.

Below is a draft of what documentation for this new feature might look like, which should explain how it would work and what value it would provide.

---

# @alias Directive on Fragment Spreads

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.

Let’s look at an example. Imagine you have a component that renders information about a `Viewer`:

```
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:

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

return
}
```

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

```
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 way to check** if `node` implements `Viewer` because `Viewer` is an abstract type!

## Enter Aliased Fragments

Aliased fragments can solve this problem. Here’s what `` would look like using them:

```
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
}
```

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!

## Inline Fragments

Inline fragments can suffer from a similar problem.

## 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:

```
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.

## Related Posts

This idea is not original, but has been floating around since before GraphQL itself was first specified.

* Dec. 2020 @josephsavona proposed fragment aliases (internal post)
* Jan. 2022 [Matt Mahoney](https://quip.com/UeHAEAnGRCB) [proposes fragment modularity](https://github.com/graphql/graphql-wg/discussions/857) as a language feature

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.