Travis
Jan 27 2021 at 18:41 GMT
Let's say that I have a Relay app in which I show a movie with its reviews. The reviews are paginated using a connection. Here's how the movie fragment
looks like:
fragment Movie_movie on Movie {
title
imageUrl
reviews(first: $count, after: $cursor) @connection(key: "Movie_movie_reviews") {
edges {
node {
id
rating
text
}
}
}
}
The user can leave a review on the movie with the following mutation:
mutation AddReviewMutation($input: AddReviewInput!) {
addReview(input: $input) {
review {
id
rating
text
}
}
}
After the mutation is executed, I would like to update the reviews connection by adding to it the newly created review so that the user can see their new review. How can I do that in Relay?
Also, if the user deletes their review, how do I remove the edge with that review from the connection? Here's the mutation to delete a review:
mutation DeleteReviewMutation($input: DeleteReviewInput!) {
deleteReview(input: $input) {
deletedReviewId
}
}
thoughtful
Jan 27 2021 at 22:33 GMT
A common way to update a connection after a mutation is by using an updater
function, which I'll cover first.
A newer and more concise way to update a connection is to use the @appendNode
, @prependNode
, and @deleteEdge
directives, which I'll cover in the end.
You can update the reviews connection by specifying an updater
function for your mutation:
commitMutation(
relayEnvironment,
{
mutation,
variables,
updater
}
);
I'll now explain how to append or prepend a new review to the connection as well as how to delete a review from the connection.
Let's see how to insert a review at the end of the connection.
We start by writing the updater
function, which takes the Relay store as argument:
const updater = (store) => {
};
The first thing we need to do is to access the payload of the addReview
mutation:
const addReviewPayload = store.getRootField('addReview');
If there's no payload, then we have nothing to update:
if (!addReviewPayload) {
return;
}
From the payload, we can access the new review:
const newReview = addReviewPayload.getLinkedRecord('review');
Next, let's get the movie to which the review was added. Since we know the movie ID, we can use store.get
:
const movie = store.get(movieId);
We can then access the reviews connection by passing the movie record and the connection key
to ConnectionHandler.getConnection
:
const reviewsConnection = ConnectionHandler.getConnection(
movie,
'Movie_movie_reviews'
);
Now, let's create an edge that will contain the new review. We can do so by passing the store, the connection to which the edge will be added, the node of the edge, and the GraphQL type name of the edge to ConnectionHandler.createEdge
:
const newReviewEdge = ConnectionHandler.createEdge(
store,
reviewsConnection,
newReview,
'ReviewEdge'
);
Finally, we can append the edge to the connection:
ConnectionHandler.insertEdgeAfter(
reviewsConnection,
newReviewEdge
);
That's it! This is how the final updater
function looks like:
const updater = (store) => {
const addReviewPayload = store.getRootField('addReview');
if (!addReviewPayload) {
return;
}
const newReview = addReviewPayload.getLinkedRecord('review');
const movie = store.get(movieId);
const reviewsConnection = ConnectionHandler.getConnection(
movie,
'Movie_movie_reviews'
);
const newReviewEdge = ConnectionHandler.createEdge(
store,
reviewsConnection,
newReview,
'ReviewEdge'
);
ConnectionHandler.insertEdgeAfter(
reviewsConnection,
newReviewEdge
);
};
If you're not comfortable working with the Relay store, I recommend to check out What is the Relay store and how to access/update the data in it? (In-depth explanation).
Let's see how to insert a review at the beginning of the connection.
The updater
function is the same as the one for appending, the only difference is that we use ConnectionHandler.insertEdgeBefore
instead of ConnectionHandler.insertEdgeAfter
.
Let's see how to delete a review from the connection.
The updater
function is very similar to the one we saw, the main difference is that instead of creating a new edge and adding it to the connection, we delete the edge that contains the node with the specified ID.
Again, we start by writing the updater
function in which we first access the mutation payload:
const updater = (store) => {
const deleteReviewPayload = store.getRootField('deleteReview');
if (!deleteReviewPayload) {
return;
}
// Continues
};
From the payload, we access the ID of the deleted review (this time using getValue
instead of getLinkedRecord
since the deletedReviewId
field is just a scalar):
const deletedReviewId = deleteReviewPayload.getValue('deletedReviewId');
Next, we access the movie and the reviews connection:
const movie = store.get(movieId);
const reviewsConnection = ConnectionHandler.getConnection(
movie,
'Movie_movie_reviews'
);
Finally, we delete the review from the connection by passing the connection and the ID of the review to ConnectionHandler.deleteNode
:
ConnectionHandler.deleteNode(
reviewsConnection,
deletedReviewId
);
This will find the edge that contains the node with the given ID and remove that edge from the connection.
That's it! Here's how the final updater
function looks like:
const updater = (store) => {
const deleteReviewPayload = store.getRootField('deleteReview');
if (!deleteReviewPayload) {
return;
}
const deletedReviewId = deleteReviewPayload.getValue('deletedReviewId');
const movie = store.get(movieId);
const reviewsConnection = ConnectionHandler.getConnection(
movie,
'Movie_movie_reviews'
);
ConnectionHandler.deleteNode(
reviewsConnection,
deletedReviewId
);
};
@appendNode
, @prependNode
, and @deleteEdge
directivesLet's see how we can achieve the same that we did above but in a more concise and declarative way by using Relay directives.
@appendNode
To append a review to the connection with @appendNode
, we need to add the @appendNode
directive on the review
field of the mutation payload.
The @appendNode
directive takes the list of connection IDs to which to append the node (in our case it's just one connection) and the name of the GraphQL type for the edge that will contain the node (in our case, it's simply "ReviewEdge"
).
We can pass the connection IDs as a variable while specifying the edge type name directly inline.
Here's how the modified AddReviewMutation
will look like:
mutation AddReviewMutation($input: AddReviewInput!, $connections: [ID!]!) {
addReview(input: $input) {
review @appendNode(connections: $connections, edgeTypeName: "ReviewEdge") {
id
rating
text
}
}
}
In order to pass the ID of the reviews connection as a variable to our mutation, we need to query the __id
field on that connection:
fragment Movie_movie on Movie {
reviews(first: $count, after: $cursor) @connection(key: "Movie_movie_reviews") {
__id
edges {
...
}
}
}
Then, we can define the variables that we pass to our mutation like this:
const variables = {
input: {...},
connections: movie.reviews ? [movie.reviews.__id] : []
};
The reason why we use the conditional operator is that the movie.reviews
connection is nullable, so we need this extra check.
That's it!
@prependNode
The process of prepending a review to the connection with @prependNode
is exactly the same as we saw for @appendNode
, the only difference is that we specify the @prependNode
directive on the review
field instead of @appendNode
.
@deleteEdge
To delete a review from the connection with @deleteEdge
, we need to add the @deleteEdge
directive on the deletedReviewId
field of the mutation payload.
The @deleteEdge
directive takes the list of connection IDs from which to delete the edge whose node has an ID equal to the one received in the mutation payload.
Here's how the modified DeleteReviewMutation
will look like:
mutation DeleteReviewMutation($input: DeleteReviewInput!, $connections: [ID!]!) {
deleteReview(input: $input) {
deletedReviewId @deleteEdge(connections: $connections)
}
}
Again, to get the ID of the connection from which we want to remove the review, we need to query the __id
field on that connection. Then, we can just access movie.reviews.__id
and pass it to the mutation:
const variables = {
input: {...},
connections: movie.reviews ? [movie.reviews.__id] : []
};
You should now have a good understanding on how to update a connection after a mutation both with an updater function as well as with Relay directives.