I believe I have read all the available resource, but still having difficulties to understand why I should explicitly handle the adding mutation on the client via getConfigs, its from example for IntroduceShipMutation mutation.
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'faction',
parentID: this.props.faction.id,
connectionName: 'ships',
edgeName: 'newShipEdge',
rangeBehaviors: {
// When the ships connection is not under the influence
// of any call, append the ship to the end of the connection
'': 'append',
// Prepend the ship, wherever the connection is sorted by age
'orderby:newest': 'prepend',
},
}];
}
If I look into server side implementation for this mutation, it returns all ships for that faction, so why client should handle this (append) manually?
outputFields: {
ship: {
type: shipType,
resolve: (payload) => data['Ship'][payload.shipId]
},
faction: {
type: factionType,
resolve: (payload) => data['Faction'][payload.factionId]
}
I can see benefit of doing this when server is sending just new ship and not the whole faction. On the other hand if client has to handle these kind of mutation itself it seems like duplicating the logic and having limited options ('append', 'prepend').
And how it all would go together with arguments for connections. Having two ship connections with argument that would filter the result - how to update when new ship is added? This new ship might appear in any of these two connections depending on filtering arguments. How would I make connections to refetch.
Thank you! And apologize in case I missed some relevant docs.
The outputFields and the fat query are closely related. It's not that the mutation always returns all of the ships for a given faction after having run, its that the mutation makes the faction field _available_ for querying as part of the IntroduceShipMutationPayload. If the client asks, the server mutation shall oblige.
Returning all of the ships as a response to adding a ship is not the goal of exposing faction though. You could think of other things the client mutation might be interested in on faction other than the edges of the ships connection, such as the total number of ships:
class IntroduceShipMutation extends Relay.Mutation {
/* ... */
getFatQuery() {
return Relay.QL`
fragment on IntroduceShipMutationPayload {
faction { ships { count } },
shipEdge,
}
`;
}
/* ... */
}
So, having added a ship, the response might look like this:
{
"data": {
"introduceShip": {
"clientMutationId": "...",
"faction": {
"id": "...",
"ships": {
"count": 10
}
},
"shipEdge": {
"cursor": "...",
"node": {
"name": "Millennium Falcon",
"id": "..."
}
}
}
}
Also, the config governs how Relay handles the optimistic response too, so whether or not you fetch the edges on the ships connection from the server, it will know where to stick the new ship before the server has even responded,
…and lastly, even if your fat query includes the ships connection with no subfields (ie. faction { ships }), Relay will strip out the edges subfield from the intersection of the fat query and the tracked query, so no refetching of all of the ships takes place. Take a look at https://github.com/facebook/relay/blob/master/src/mutation/RelayMutationQuery.js#L213-L221
I'll make it my job to make this more clear in the mutations docs. Thanks!
Right, thanks. I understand that this covers many use cases in optimal way and definitely makes sense.
How about if the mutation have some more complicated logic and would for example insert ship somewhere in the middle. I wonder what options I have when I want make Relay to refetch ships. Is updating facet using FIELDS_CHANGE way to go?
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {faction: this.props.faction.id},
}];
}
Exactly!
Leaving this issue open to track improvements to the mutations guide that cover these topics.
I am not sure if this is in the docs but any connection not listed in the RANGE_ADD's rangeBehaviors will be refetched from the server and we won't be able to do an optimistic append or prepend for those at the moment. So if you are adding a ship and you also have fetched for orderby:size, that connection would be refetched from the server automatically with the RANGE_ADD config.
I also would like better documentation for this. It definitely seems confusing at first, especially that coming into it, I think people are confused why mutations have to be so verbose, expecting Relay to be somehow "smarter".
In particular, the RANGE_ADD configuration. Here's the best that I can figure it:
return [{
type: 'RANGE_ADD',
rangeBehaviors: {
'': 'append'
},
// Relay should add which "edge" to a connection?
edgeName: 'newItemEdge',
// Which connection? "items" on that dataId
parentID: this.props.menu.id,
connectionName: 'items',
// But why do we need to tell it this?
parentName: 'menu',
}];
So Relay takes edgeName from the response, takes the object with parentID from it's cache. It then adds the new edge to the connection field "connectionName" of the "parentID" object.
That leaves me with some questions:
viewer { items { tags }) - Is the parent the viewer or the items?I would assume that now, in 99% of the cases, when "parentID" only has one connection of the given type, so it should't even be necessary to tell Relay about "connectionName" in those cases?
Are you suggesting that we scan all fields on the parentID to find the field which is a connection and happens to contain an edges type that matches the type of the returned connection edge? You are certainly right that this could be made easier for a large portion of use cases.
Why is "parentName" used for?
The parentName is the field on the response payload that corresponds to the parent field. This is important in the case that fields on the parent need to be updated.
I agree that it's confusing which of these configurations apply to which things — the response payload? the client data? the schema?
The RelayMutation docblock might help make these clearer: https://github.com/facebook/relay/blob/fbf8c50825ff609a5ab101a934f0b9e165ab5d9b/src/mutation/RelayMutation.js#L114-L129
Would also like to know how to specify parentName when connection is nested (viewer { cart { items } })
I'm going to close this now because the mutations API is going to be drastically improved, simpler and different in Relay 2, so we aren't likely to be able to dedicate any resources to documenting the old system at this time. (Even so, if somebody wants to submit a PR improving the docs I'll be happy to review it.)
Thanks to everybody who contributed to this thread!
(Just in case anybody stumbles on this thread looking for more info on mutations in the future: since this thread was originally started, we've talked a little bit about the concepts behind mutations: for example, see the videos on the videos page).
Most helpful comment
Would also like to know how to specify parentName when connection is nested (
viewer { cart { items } })