Relay makes use of GraphQL mutations; operations that enable us to mutate data on the client and server. To create a mutation for use in our app, we subclass Relay.Mutation
and implement, at minimum, the four abstract methods listed below.
Properties
Methods
static fragments: RelayMutationFragments<$Keys<Tp>> // Type of RelayMutationFragments type RelayMutationFragments<Tk> = { [key: Tk]: FragmentBuilder; }; // Type of FragmentBuilder type FragmentBuilder = (variables: Variables) => RelayConcreteNode;
We declare our mutations' data dependencies here, just as we would with a container. This is particularly useful to ensure that a set of fields we might want to use in this mutation's optimistic response have been fetched.
class LikeStoryMutation extends Relay.Mutation { static fragments = { story: () => Relay.QL` fragment on Story { likers { count }, viewerDoesLike, } `, }; getOptimisticResponse() { // this.props.story.likers.count and this.props.story.viewerDoesLike // are guaranteed to have been fetched since we've declared // them to be part of this mutation's data dependencies above. return { /* ... */ }; } }
See also: Mutations > Fragment variables and Mutations > Optimistic updates
static initialVariables: {[name: string]: mixed};
The defaults we specify here will become available to our fragment builders:
class ChangeTodoStatusMutation extends Relay.Mutation { static initialVariables = {orderby: 'priority'}; static fragments = { todos: () => Relay.QL` # The variable defined above is available here as $orderby fragment on User { todos(orderby: $orderby) { ... } } `, }; /* ... */ }
See also: Mutations > Fragment variables
static prepareVariables: ?( prevVariables: {[name: string]: mixed}, route: RelayMetaRoute, ) => {[name: string]: mixed} // Type of `route` argument type RelayMetaRoute = { name: string; }
If we provide to a mutation a method that conforms to the signature described above, it will be given the opportunity to modify the fragment builders' variables, based on the previous variables (or the initialVariables
if no previous ones exist), the meta route, and the runtime environment. Whatever variables this method returns will become available to this mutation's fragment builders.
class BuySongMutation extends Relay.Mutation { static initialVariables = {format: 'mp3'}; static prepareVariables = (prevVariables) => { var overrideVariables = {}; var formatPreference = localStorage.getItem('formatPreference'); if (formatPreference) { overrideVariables.format = formatPreference; // Lossless, hopefully } return {...prevVariables, overrideVariables}; }; /* ... */ }
See also: Mutations > Fragment variables
Create a mutation instance using the new
keyword, optionally passing it some props. Note that this.props
is not available inside the constructor function, but are set for all the methods mentioned below (getCollisionKey
, getOptimisticResponse
, etc). This restriction is due to the fact that mutation props may depend on data from the RelayEnvironment, which isn't known until the mutation is applied with applyUpdate
or commitUpdate
.
var bookFlightMutation = new BuyPlaneTicketMutation({airport: 'yvr'}); Relay.Store.commitUpdate(bookFlightMutation);
abstract getConfigs(): Array<{[key: string]: mixed}>
Implement this required method to give Relay instructions on how to use the response payload from each mutation to update the client-side store.
class LikeStoryMutation extends Relay.Mutation { getConfigs() { return [{ type: 'FIELDS_CHANGE', fieldIDs: { story: this.props.story.id, }, }]; } }
See also: Mutations > Mutator configuration
abstract getFatQuery(): GraphQL.Fragment
Implement this required method to design a ‘fat query’ – one that represents every field in your data model that could change as a result of this mutation.
class BuySongMutation extends Relay.Mutation { getFatQuery() { return Relay.QL` fragment on BuySongPayload { songs { count, edges, }, totalRunTime, } `, } }
See also: Mutations > The fat query
abstract getMutation(): GraphQL.Mutation
Implement this required method to return a GraphQL mutation operation that represents the mutation to be performed.
class LikeStoryMutation extends Relay.Mutation { getMutation() { return this.props.story.viewerDoesLike ? return Relay.QL`mutation {unlikeStory}` : return Relay.QL`mutation {likeStory}`; } }
abstract getVariables(): {[name: string]: mixed}
Implement this required method to prepare variables to be used as input to the mutation.
class DestroyShipMutation extends Relay.Mutation { getVariables() { return { // Assume that the server exposes a `destroyShip` mutation // that accepts a `shipIDToDestroy` variable as input: shipIDToDestroy: this.props.ship.id, }; } }
Warning
The term ‘variables’ here refers to the input to the server-side mutation, not to the variables made available to this mutation's fragment builders.
static getFragment( fragmentName: $Keys<Tp>, variableMapping?: Variables ): RelayFragmentReference // Type of the variableMapping argument type Variables = {[name: string]: mixed};
Gets a fragment reference for use in a parent's query fragment.
class StoryComponent extends React.Component { /* ... */ static fragments = { story: () => Relay.QL` fragment on Story { id, text, ${LikeStoryMutation.getFragment('story')}, } `, }; }
You can also pass variables to the mutation's fragment builder from the outer fragment that contains it.
class Movie extends React.Component { /* ... */ static fragments = { movie: () => Relay.QL` fragment on Movie { posterImage(lang: $lang) { url }, trailerVideo(format: $format, lang: $lang) { url }, ${RentMovieMutation.getFragment('movie', { format: variables.format, lang: variables.lang, })}, } `, }; }
Hint
In that last example, think of
$format
andvariables.format
as the same value.
getCollisionKey(): ?string
Implement this method to return a collision key. Relay will send any mutations having the same collision key to the server serially and in-order.
class LikeStoryMutation extends Relay.Mutation { getCollisionKey() { // Give the same key to like mutations that affect the same story return `like_${this.props.story.id}`; } }
getFiles(): ?FileMap // Type of the FileMap object type FileMap = {[key: string]: File};
Implement this method to return a map of File
objects to upload as part of a mutation.
class AttachDocumentMutation extends Relay.Mutation { getFiles() { return { file: this.props.file, }; } } class FileUploader extends React.Component { handleSubmit() { var fileToAttach = this.refs.fileInput.files.item(0); Relay.Store.commitUpdate( new AttachDocumentMutation({file: fileToAttach}) ); } }
getOptimisticConfigs(): Array<{[key: string]: mixed}>
Implement this method in cases where the mutator configuration needed to handle the optimistic response needs to be different than the one that handles the server response.
See also: Relay.Mutation::getConfigs()
getOptimisticResponse(): ?{[key: string]: mixed}
Implement this method to craft an optimistic response having the same shape as the server response payload. This optimistic response will be used to preemptively update the client cache before the server returns, giving the impression that the mutation completed instantaneously.
class LikeStoryMutation extends Relay.Mutation { getOptimisticResponse() { return { story: { id: this.props.story.id, likers: { count: this.props.story.likers.count + 1, }, viewerDoesLike: !this.props.story.viewerDoesLike, }, }; } }
See also: Mutations > Optimistic updates