Skip to content

Commit

Permalink
Consolidate updating connections guides
Browse files Browse the repository at this point in the history
Reviewed By: kassens

Differential Revision: D26898122

fbshipit-source-id: 8d2d37a83e879bc08f4da65135031a8f586f59ff
  • Loading branch information
Juan Tejada authored and facebook-github-bot committed Mar 9, 2021
1 parent 7fa7c60 commit ae82bb3
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 226 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const userFragment = graphql`

* In the example above, we're querying for the `friends` field, which is a connection; in other words, it adheres to the connection spec. Specifically, we can query the `edges` and `node`s in the connection; the `edges` usually contain information about the relationship between the entities, while the `node`s are the actual entities at the other end of the relationship; in this case, the `node`s are objects of type `User` representing the user's friends.
* In order to indicate to Relay that we want to perform pagination over this connection, we need to mark the field with the `@connection` directive. We must also provide a *static* unique identifier for this connection, known as the `key`. We recommend the following naming convention for the connection key: `<fragment_name>_<field_name>`.
* We will go into more detail later as to why it is necessary to mark the field as a `@connection` and give it a unique `key` in our [Adding and Removing Items from a Connection](../adding-and-removing-items/) section.
* We will go into more detail later as to why it is necessary to mark the field as a `@connection` and give it a unique `key` in our [Updating Connections](../updating-connections/) section.


In order to render this fragment which queries for a connection, we can use the `usePaginationFragment` Hook:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
---
id: adding-and-removing-items
title: Adding and Removing Items from Connections
slug: /guided-tour/list-data/adding-and-removing-items/
id: updating-connections
title: Updating Connections
slug: /guided-tour/list-data/updating-connections/
---

import DocsRating from '../../../src/core/DocsRating';
import {OssOnly, FbInternalOnly} from 'internaldocs-fb-helpers';

Usually when you're rendering a connection, you'll also want to be able to add or remove items to/from the connection in response to user actions.

As explained in our [Updating Data](../../updating-data/) section, Relay holds a local in-memory store of normalized GraphQL data, where records are stored by their IDs. When creating mutations, subscriptions, or local data updates with Relay, you must provide an `updater` function, inside which you can access and read records, as well as write and make updates to them. When records are updated, any components affected by the updated data will be notified and re-rendered.
As explained in our [Updating Data](../../updating-data/) section, Relay holds a local in-memory store of normalized GraphQL data, where records are stored by their IDs. When creating mutations, subscriptions, or local data updates with Relay, you must provide an [`updater`](../../updating-data/graphql-mutations/#updater-functions) function, inside which you can access and read records, as well as write and make updates to them. When records are updated, any components affected by the updated data will be notified and re-rendered.


## Connection Records

In Relay, connection fields that are marked with the `@connection` directive are stored as special records in the store, and they hold and accumulate *all* of the items that have been fetched for the connection so far. In order to add or remove items from a connection, we need to access the connection record using the connection `*key*`, which was provided when declaring a `@connection`; specifically, this allows us to access a connection inside an `updater` function using the `ConnectionHandler` APIs.
In Relay, connection fields that are marked with the `@connection` directive are stored as special records in the store, and they hold and accumulate *all* of the items that have been fetched for the connection so far. In order to add or remove items from a connection, we need to access the connection record using the connection `key`, which was provided when declaring a `@connection`; specifically, this allows us to access a connection inside an [`updater`](../../updating-data/graphql-mutations/#updater-functions) function using the `ConnectionHandler` APIs.

For example, given the following fragment that declares a `@connection`:
For example, given the following fragment that declares a `@connection`, We can access the connection record inside an `updater` function in a few different ways:

```js
const {graphql} = require('react-relay');
Expand All @@ -34,14 +34,74 @@ const storyFragment = graphql`
`;
```

### Accessing connections using `__id`

We can access the connection record inside an `updater` function using `ConnectionHandler.getConnection`:
We can query for a connection's `__id` field, and then use that `__id` to access the record in the store:

```js
const fragmentData = useFragment(
graphql`
fragment StoryComponent_story on Story {
comments @connection(key: "StoryComponent_story_comments_connection") {
# Query for the __id field
__id
# ...
}
}
`,
props.story,
);

// Get the connection record id
const connectionID = fragmentData?.comments?.__id;
```
Then use it to access the record in the store:
```js
function updater(store: RecordSourceSelectorProxy) {
// connectionID is passed as input to the mutation/subscription
const connection = store.get(connectionID);

// ...
}
```
### Accessing connections using `ConnectionHandler.getConnectionID`
If we have access to the ID of the parent record that holds the connection, we can access the connection record by using the `ConnectionHandler.getConnectionID` API:
```js
const {ConnectionHandler} = require('relay-runtime');

function updater(store: RecordSourceSelectorProxy) {
// Get the connection ID
const connectionID = ConnectionHandler.getConnectionID(
storyID, // passed as input to the mutation/subscription
'StoryComponent_story_comments_connection',
);

// Get the connection record
const connectionRecord = store.get(connectionID);

// ...
}
```
### Accessing connections using `ConnectionHandler.getConnection`
If we have access to the parent record that holds the connection, we can access the connection record via the parent, by using the `ConnectionHandler.getConnection` API:
```js
const {ConnectionHandler} = require('relay-runtime');

function updater(store: RecordSourceSelectorProxy) {
// Get parent story record
// storyID is passed as input to the mutation/subscription
const storyRecord = store.get(storyID);

// Get the connection record from the parent
const connectionRecord = ConnectionHandler.getConnection(
storyRecord,
'StoryComponent_story_comments_connection',
Expand All @@ -53,7 +113,119 @@ function updater(store: RecordSourceSelectorProxy) {
## Adding Edges
Once we have a connection record, we also need a record for the new edge that we want to add to the connection. Usually, mutation or subscription payloads will contain the new edge that was added; if not, you can also construct a new edge from scratch.
There are a couple of alternatives for adding edges to a connection:
### Using declarative directives
Usually, mutation or subscription payloads will expose the new edge that was added on the server as an edge field. If your mutation or subscription exposes an edge field that you can query for in the response, then you can use the `@appendEdge`, `@prependEdge` , `@prependNode`, `@appendNode` declarative mutation directives on the edge field in order to add the newly created edge to the connection (note that these directives also work on queries).
These directives accept a `connections` parameter, which needs to be a GraphQL variable containing an array of connection IDs. Connection IDs can be obtained either by using the [`__id` field on connections](#accessing-connections-using-__id) or using the [`ConnectionHandler.getConnectionID`](#accessing-connections-using-connectionhandlergetconnectionid) API.
#### `@appendEdge` / `@prependEdge`
These directives work on edge fields. `@prependEdge` will add the selected edge to the beginning of each defined in the `connections` array, whereas `@appendEdge` will add the selected edge to the end of each connection in the array.
**Arguments:**
- `connections`: An array of connection IDs. Connection IDs can be obtained either by using the [`__id` field on connections](#accessing-connections-using-__id) or using the [`ConnectionHandler.getConnectionID`](#accessing-connections-using-connectionhandlergetconnectionid) API.
**Example:**
```js
// Get using the `__id` field
const connectionID = fragmentData?.comments?.__id;

// Or get using `ConnectionHandler.getConnectionID()`
const connectionID = ConnectionHandler.getConnectionID('<story-id>', 'StoryComponent_story_comments_connection')

// ...

// Mutation
commitMutation<AppendCommentMutation>(environment, {
mutation: graphql`
mutation AppendCommentMutation(
# Define a GraphQL variable for the connections array
$connections: [String!]!
$input: CommentCreateInput
) {
commentCreate(input: $input) {
# Use @appendEdge or @prependEdge on the edge field
feedbackCommentEdge @appendEdge(connections: $connections) {
cursor
node {
id
}
}
}
}
`,
variables: {
input,
// Pass the `connections` array
connections: [connectionID],
},
});
```
#### `@appendNode` / `@prependNode`
These directives work on Node fields, and will create edges with the specified `edgeTypeName`. `@prependNode` will add the selected node to the beginning of each defined in the `connections` array, whereas `@appendNode` will add the selected node to the end of each connection in the array.
**Arguments:**
- `connections`: An array of connection IDs. Connection IDs can be obtained either by using the [`__id` field on connections](#accessing-connections-using-__id) or using the [`ConnectionHandler.getConnectionID`](#accessing-connections-using-connectionhandlergetconnectionid) API.
- `edgeTypeName`: The typename of an edge, corresponding to the edge type argument in `ConnectionHandler.createEdge`.
**Example:**
```js
// Get using the `__id` field
const connectionID = fragmentData?.comments?.__id;

// Or get using `ConnectionHandler.getConnectionID()`
const connectionID = ConnectionHandler.getConnectionID('<story-id>', 'StoryComponent_story_comments_connection')

// ...

// Mutation
commitMutation<AppendCommentMutation>(environment, {
mutation: graphql`
mutation AppendCommentMutation(
# Define a GraphQL variable for the connections array
$connections: [String!]!
$input: CommentCreateInput
) {
commentCreate(input: $input) {
# Use @appendNode or @prependNode on the edge field
feedbackCommentNode @appendNode(connections: $connections, edgeTypeName: "CommentsEdge") {
id
}
}
}
`,
variables: {
input,
// Pass the `connections` array
connections: [connectionID],
},
});
```
#### Order of execution
For all of these directives, they will be executed in the following order within the mutation or subscription, as per the [order of execution of updates](../../updating-data/graphql-mutations/#order-of-execution-of-updater-functions):
* When the mutation is initiated, after the optimistic response is handled, and after the optimistic updater function is executed, the `@prependEdge` and `@appendEdge` directives wills be applied to the optimistic response.
* If the mutation succeeds, after the data from the network response is merged with the existing values in the store, and after the updater function is executed, the `@prependEdge` and `@appendEdge` directives will be applied to the data in the network response.
* If the mutation failed, the updates from processing the `@prependEdge` and `@appendEdge` directives will be rolled back.
### Manually adding edges
The directives described [above](#using-declarative-directives) largely remove the need to manually add and remove items from a connection, however, they do not provide as much control as you can get with manually writing an updater, and may not fulfill every use case.
In order to write an updater to modify the connection, we need to make sure we have access to the [connection record](#connection-record). Once we have the connection record, we also need a record for the new edge that we want to add to the connection. Usually, mutation or subscription payloads will contain the new edge that was added; if not, you can also construct a new edge from scratch.
For example, in the following mutation we can query for the newly created edge in the mutation response:
Expand All @@ -79,7 +251,7 @@ const createCommentMutation = graphql`
* Note that we also query for the `cursor` for the new edge; this isn't strictly necessary, but it is information that will be required if we need to perform pagination based on that `cursor`.
Inside an `updater`, we can access the edge inside the mutation response using Relay store APIs:
Inside an [`updater`](../../updating-data/graphql-mutations/#updater-functions), we can access the edge inside the mutation response using Relay store APIs:
```js
const {ConnectionHandler} = require('relay-runtime');
Expand Down Expand Up @@ -112,7 +284,6 @@ function updater(store: RecordSourceSelectorProxy) {
* Note that we need to construct the new edge from the edge received from the server using `ConnectionHandler.buildConnectionEdge` before we can add it to the connection.

If you need to create a new edge from scratch, you can use `ConnectionHandler.createEdge`:
```js
Expand Down Expand Up @@ -172,13 +343,57 @@ function updater(store: RecordSourceSelectorProxy) {
* Note that these APIs will *mutate* the connection in place
:::note
Check out our complete [Relay Store APIs](../../../api-reference/store/).
:::
## Removing Edges
> NOTE: Check out our complete Relay Store APIs [here](https://facebook.github.io/relay/docs/en/relay-store.html)
### Using declarative deletion directive
Similarly to the [directives to add edges](#using-declarative-directives), we can use the `@deleteEdge` directive to delete edges from connections. If your mutation or subscription exposes a field with the ID or IDs of the nodes that were deleted that you can query for in the response, then you can use the `@deleteEdge` directive on that field to delete the respective edges from the connection (note that this directive also works on queries).
#### `@deleteEdge`
Works on GraphQL fields that return an `ID` or `[ID]`. Will delete the edge with nodes that matches the `id` from each connection defined in the `connections` array.
**Arguments:**
- `connections`: An array of connection IDs. Connection IDs can be obtained either by using the [`__id` field on connections](#accessing-connections-using-__id) or using the [`ConnectionHandler.getConnectionID`](#accessing-connections-using-connectionhandlergetconnectionid) API.
## Removing Edges
**Example:**
```js
// Get using the `__id` field
const connectionID = fragmentData?.comments?.__id;

// Or get using `ConnectionHandler.getConnectionID()`
const connectionID = ConnectionHandler.getConnectionID('<story-id>', 'StoryComponent_story_comments_connection')

// ...

// Mutation
commitMutation<AppendCommentMutation>(environment, {
mutation: graphql`
mutation AppendCommentMutation(
# Define a GraphQL variable for the connections array
$connections: [String!]!
$input: CommentCreateInput
) {
commentsDelete(input: $input) {
deletedCommentIds @deleteEdge(connections: $connections)
}
}
`,
variables: {
input,
// Pass the `connections` array
connections: [connectionID],
},
});
```
### Manually removing edges
`ConnectionHandler` provides a similar API to remove an edge from a connection, via `ConnectionHandler.deleteNode`:
Expand All @@ -204,17 +419,18 @@ function updater(store: RecordSourceSelectorProxy) {
* Note that this API will *mutate* the connection in place.


> Remember: When performing any of the operations described here to mutate a connection, any fragment or query components that are rendering the affected connection will be notified and re-render with the latest version of the connection.
:::note
Remember: when performing any of the operations described here to mutate a connection, any fragment or query components that are rendering the affected connection will be notified and re-render with the latest version of the connection.
:::
## Connection Identity With Filters
In our previous examples, our connections didn't take any arguments as filters. If you declared a connection that takes arguments as filters, the values used for the filters will be part of the connection identifier. In other words, *each of the values passed in as connection filters will be used to identify the connection in the Relay store.*

> Note that this excludes pagination arguments, i.e. it excludes `first`, `last`, `before`, and `after`.
:::note
Note that this excludes pagination arguments, i.e. it excludes `first`, `last`, `before`, and `after`.
:::
For example, let's say the `comments` field took the following arguments, which we pass in as GraphQL [variables](../../rendering/variables/):
Expand Down
Loading

0 comments on commit ae82bb3

Please sign in to comment.