- Notifications
You must be signed in to change notification settings - Fork0
A guide to learn about Relay mutations.
License
pt-br/relay-mutation-guide
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
- I'm freaking out about how to handle mutations on GraphQL and Relay;
- I tried reading someone else's code, but it's not too much explained and it's complex;
- The examples I found on internet have less explanation and a lots of code that will output something only 1h after going through the tutorial;
- Star Wars' example of facebook sucks.
In this guide we will be creating a simple application that uses a real world example to get things easier for you.It's important to say that this is not a complete guide for GraphQL and Relay, the focus of this guide is only onMutations.
So, before you get started, be sure to know at least the basics of:
- ES6
- React
- Relay (how it fits into a React application)
- GraphQL
- GraphiQL
As I said, we will be working on an application for this guide. You can check it running accessing ademo build on heroku. I've prepared a special boilerplate of this application, removing everything related to mutations, so we can do it together on this guide.
Let's put the hand on the code, start by clonning the boilerplate:
git clone https://github.com/pt-br/relay-phones-guide.git
Go inside the folder you've clonned the repository, and install the node modules:
npm install
Don't worry about the code for now, just start the server by running:
npm start
Go tohttp://localhost:3000 and check if you can see the application running. If not, get back to the first step.
Ok, if you are here you already have the environment running. Let's go ahead.
On a real application, the database usually would be based into some database management system, REST APIs, etc. For this guide, our database is pure JavaScript.
We have three class files:Database.js,User.js andPhone.js.Database.js is our main entry class for database operations. GraphQL queries and mutations will comunicate with this file to read and change data.User.js is a class that contains its own methods and contains instances ofPhone.js as well.
I'm not going deeper inside of this files, but you can read them if you want - They are pretty well commented and I'm sure you'll easily understand how they work.
PS: GraphQL isnot a database, don't misunderstand that.
A mutation is an operation that creates, changes or erases something (for reading, we use queries and not mutations). A mutation will always do something, then fetch something.
It's important to know that a mutation affects both sides of your application: server(GraphQL) and client(Relay).
Let's start by creating a mutation to add a new phone.
Go through/data/schema.js
and observe that we are importing some GraphQL and GraphQL-Relay items in there.
We are also importing ourDatabase.js class and creating an instance of it inside of the schema. This is required in order to make possible doing manipulations into our database by using GraphQL, in other words: GraphQL will be able to access methods inside ofDatabase.js.
Onschema.js
, write the following mutation at line 96:
constAddPhoneMutation=mutationWithClientMutationId({name:'AddPhone',inputFields:{model:{type:newGraphQLNonNull(GraphQLString),},image:{type:newGraphQLNonNull(GraphQLString),},},outputFields:{viewer:{type:UserType,resolve:()=>database.getUser(),},},mutateAndGetPayload:({ model, image})=>{constnewPhone=database.insertPhone(model,image);returnnewPhone;},});
Explanation:mutationWithClientMutationId
is a helper to build mutations from the packagegraphql-relay
. You don't need to know much more about this, you only need to know a few things:
- It takes a name, as we used
AddPhone
- It takes inputFields;
- It takes outPutFields;
- It takes a mutation method (mutateAndGetPayload).
It's very important to have aname for our mutations. It will be used both on GraphQL and Relay.
On theinputFields we must declare the things we are going to send for the mutation. Once we are going to add a new Phone, we must send amodel
and animage
fields, that are the required data to create a new Phone into our database.
On theoutputFields we are declaring what will be output by the mutation, in this case, we are just returning our User (that contains Phones).
OnmutateAndGetPayload we are using data provided by theinputFields, in this case,model
andimage
. Then we call a function from ourDatabase.js calledinsertPhone
, we are sending bothmodel
andimage
to this function. Inside ofinsertPhone
, there's a return for the new phone being added, and this is what we are returning onmutateAndGetPayload.
It got simple for you now? I hope so :p
Ok, you have a mutation and now you need to tell GraphQL that you do.
Like the Root query, you must specify a "root mutation" that will contain all of your mutations.
Onschema.js
at line 134, write your root mutation:
constMutation=newGraphQLObjectType({name:'Mutation',fields:()=>({addPhone:AddPhoneMutation,}),});
I think it's self-explanatory.
Then, at line 145, modify your Schema export to contain the root mutation. It should be like this:
exportconstSchema=newGraphQLSchema({query:Root, mutation:Mutation,});
Ok, the GraphQL side of the mutation is done!
It's important to notice that every change on theschema.js
will require you to stop the server and run a command to update the schema. In order to update our schema, let's do it now.
Stop the server, then run:
npm run update-schema
And then start the server again:
npm start
Alright, your schema is now updated and you can test your mutation.
Let's go to our GraphiQL instance that is running onhttp://localhost:8080, I strongly recommend you to run it on a separate tab.
Run the following mutation on the left pannel:
mutation{addPhone(input:{clientMutationId:"1"model:"Nexus 5",image:"https://goo.gl/Fq46CZ"}){viewer{phones{edges{node{ modelimage}}}}}}
Explanation: We are calling the mutation byaddPhone
, the same name we have exported on our Root Mutation. Then, we need to supply the inputs(remember when we declared it on theinputFields?).
As you can notice, we are sending a parameter that was not into ourinputFields declaration -
clientMutationId
. You don't need to worry about this field, it will be automatically filled by Relay under the hood. We only need to spoof it on GraphiQL(and any string value should work).What comes after the
addPhone
is theoutputFields stuff.
After adding the Nexus 5 using our mutation, you can refresh the application athttp://localhost:3000 and check that the phone is there.
Yes, your mutation worked pretty good!
Probably this is the part of the story you have been always stuck in, but don't worry, this time it will work.
Let's create the Relay part of your mutation - Go to/js/mutations
folder and create a file namedAddPhoneMutation.js
.
Inside of this file, write the following code:
importRelayfrom'react-relay';exportdefaultclassAddPhoneMutationextendsRelay.Mutation{staticfragments={viewer:()=>Relay.QL` fragment on User { id } `,};getMutation(){returnRelay.QL`mutation{addPhone}`;}getVariables(){let{ phoneModel, phoneImage}=this.props;/** * If the fields come empty, force them to be null. * This way, it won't be filled on GraphQL. */phoneModel.length===0 ?phoneModel=null :false;phoneImage.length===0 ?phoneImage=null :false;return{model:phoneModel,image:phoneImage,};}getFatQuery(){returnRelay.QL` fragment on AddPhonePayload { viewer } `;}getConfigs(){const{ viewer}=this.props;return[{type:"FIELDS_CHANGE",fieldIDs:{viewer:viewer.id}}];}}
Explanation: At the first moment it could be strange for you, but it will make sense.
First of all, there's a sequence of the methods execution - I'll be explainig them at the same order they are executed. Also, part of these functions will be responsible tosend data and other part will behandling the application after that the modifications happen.
Firstly, we declare a static method called
fragments
. This holds the required data to run our mutation - In this case, ourUser
idThe
getMutation()
method uses theRelay.QL
(you can read more about thishere) method to specify which mutation we'll be operating. Remember when we exported our mutation into our Root Mutation? That's what we must use here.On
getVariables()
we will be collecting the necessary data to send to ourinputFields. Remember when we create them into our mutation on GraphQL? This function must return an object that matches exactly theinputFields we've declared on theschema
. You will be sending this data byprops
, inside of your application from your React components. - We'll be reviewing this later.On
getFatQuery()
we will be returning a fragment containing everything that could change as a result of this mutation. In our case, what changes here is ourUser
(viewer). You can notice that the fragment name of the Fat Query isAddPhonePayload
, but this is not declared anywhere - Yes, it isn't. This name is auto generated internally and it just concats the name of your mutation (AddPhone
) + Payload. Remember when we declared the name of your mutation on theschema
?At
getConfigs()
we advise how Relay should handle theAddPhonePayload
returned by the server. In our case, we are using aFIELDS_CHANGE
type (because our User has changed, now it has more Phones than before) and we specify theid
of what is being changed, ourviewer
id (that is theUser
id). Here, like we passed theinputFields data togetVariables()
, we must pass theviewer
as aprop
too. - We'll be reviewing this later.
At this point, your mutation should be working fine.
Now, we must configure how we will be sending the necessary data to ourAddPhoneMutation.js
(asprops
) into our React application, and setting how to call this mutation internally.
You've learned a lot until here, and you are almost close to get you mutation working by Relay. Let's adapt out React components to receive and call ourAddPhoneMutation.js
.
Go throughviews/PhoneView.js
and import our mutation file at line 5:
importAddPhoneMutationfrom'../mutations/AddPhoneMutation';
Scroll down until you reach theRelay Container
of this view at line 119. Modify the fragment, including the mutation fragment at line 123. It should look like this:
exportdefaultRelay.createContainer(PhoneView,{fragments:{viewer:()=>Relay.QL` fragment on User {${AddPhoneMutation.getFragment('viewer')} phones(first: 908098879) { edges { node { phoneId model image }, }, }, } `,},});
Ok, now your view is ready to receive the modifications comming from the mutation - It's time to set up how we will trigger it and pass the data asprops
.
If you check the line 51 ofPhoneView.js
you will notice that we have a component calledAddModal
:
<AddModalviewer={viewer}handleModal={this.handleModal}/>
This component receives twoprops
:viewer
andhandleModal
. Theviewer
comes from ourRelay Container
as a prop
that Relay send to our React components. We are sending this toAddModal
because we're going to call our mutation inside of this component - So let's do it.
Go tojs/components/AddModal.js
and import our mutation here too, at line 4:
importAddPhoneMutationfrom'../mutations/AddPhoneMutation';
To keep this guide as simple as possible, theAddModal
component already have all the logic implemented, so you just need to worry about the mutation process.
Go to line 22, you will notice there’s a function in there calledaddPhone
- This function is triggered when user clicks on theAdd
button, that is on the modal.
Remove thealert
that is inside of this function and let’s start working into passing data to our mutation.
The form inside of this modal has twouncontrolled inputs, their refs arephoneModelInput
andphoneImageInput
, and we are going to get its values to send as data for our mutation.
We will start declarating some constants inside of our function, so, right below theaddPhone()
declaration, write the following code:
const{ viewer}=this.props;const{ phoneModelInput, phoneImageInput}=this.refs;
Now we have three variables:viewer
(that we are receiving asprops
) andphoneModelInput
/phoneImageInput
that are our inputs.
Ok, now we need to call our mutation sending the required data to our mutation. For this, we'll be using thecommitUpdate()
method fromRelay.Store API. This is one of the methods we can use to trigger a mutation.
ThecommitUpdate()
method accepts two callbacks to be triggered after the server response:
onSuccess
is called if the mutation succeeded.onFailure
is called if the mutation failed.
We will be implementing both, so we can close theAddModal
when we reachonSuccess
or show an error when we got anonFailure
callback. In order to do that, let's implementcommitUpdate()
into ouraddPhone()
function, it should look like this:
addPhone=()=>{const{ viewer}=this.props;const{ phoneModelInput, phoneImageInput}=this.refs;Relay.Store.commitUpdate(newAddPhoneMutation({ viewer,phoneModel:phoneModelInput.value,phoneImage:phoneImageInput.value,}),{onFailure:(transaction)=>{this.setState({error:'Something went wrong, please try again.',});},onSuccess:(response)=>{this.close();},},);};
Explanation: We are calling thecommitUpdate()
from Relay.Store, then we create a new instance of our mutation by usingnew AddPhoneMutation
- At this point, we will be sending theprops
we need inside our mutation.
The
viewer
prop will be used inside of the mutation on thegetConfigs()
function (Remember that?)The
phoneModel
andphoneImage
are receiving the value from our inputs and will be used bygetVariables()
function inside of the mutation. Then that function will be sending this data to theinputFields we declared at theschema
(Makes sense now, right? xD)At the
onFailure
callback we are setting astate
that will just render an error into ourAddModal
component (You can reach this callback by clickingAdd
without filling the inputs).At the
onSuccess
callback we just call a function that closes our modal.
Ok then, time to test it! Go tohttp://localhost:3000 and try adding a new Phone.
If you have followed this guide correctly, you should be able to addPhones
toUsers
, what means thatYOUR MUTATION IS WORKING!
Now that you know exactly how a mutation work, why don't you try to implement two more mutations to delete and editPhones
? It will be fun!
You can also check thefull application in case you need to know how I've implemented these two mutations.
I hope this guide have helped you!