Relay 102: Mutations
Last time in Relay 101 we walked through how to build a Hacker News app using Relay. Now we’re on to mutations.
Mutation means updating data on the server. Some apps only need to present data from the server, but many projects need to make updates.
Say you’re on Facebook and decide to change your profile picture. Your photo appears in a lot of places throughout the app: on your profile header of course, but also in chats, on your posts, beside your comments, all kinds of nooks and crannies. In a perfect world your profile photo should change in all those places simultaneously and, since we already have a copy of the image locally, without waiting for a server response .
How close are we to achieving that ideal place in most React apps? Well you could do everything by hand with a Flux-ful system, but wiring the various actions and updates for every single type of data synchronization is tedious and inevitably error-prone. But if you use Relay, you can get it for free.
Relay achieves this by 1) leveraging GraphQL to differentiate mutations from normal queries 2) tracking what local state is being mutated 3) allowing us to immediately update local state instead of waiting for a server response (“optimistically” in Relay nomenclature).
Relay requires your GraphQL schema to behave in certain ways — for example, each object must have a globally-unique string ID. This was implicit in 101's Hacker News example, but mutations require a bit more understanding beforehand.
The rules for Relay-compatible mutations are:
- They are defined at the top level of your mutation hierarchy
- They must accept only one argument, referred to as the Input
- They return a structure referred to as the Payload
- The Input and Payload must pass along Relay-specific fields in addition to fields relevant to your application
We won’t go into the nitty-gritty of what that GraphQL looks like since we’re not implementing a GraphQL backend in this article — GraphQLHub is ready for us.
GraphQLHub exposes a simple in-memory key-value store; we’re going to build a simple app that allows us to query the store and mutate its values. It’s definitely a simple example but works to demonstrate the important concepts of Relay mutations.
Now that our project is setup, lets code our application inside index.js. We’ll start by rendering a simple React component:
Execute “./node_moduels/.bin/webpack-dev-server” in your terminal and open http://localhost:8080/webpack-dev-server/ in your browser:
We want to build a simple UI to query keys from the server. We covered this in 101, but as a refresher a Relay component consists of:
- A React Component
- Creating a higher-order component via Relay.createContainer
- A subclass of Relay.Route
- Mounting a Relay.RootContainer component
Our React component will be the Explorer. We’ll update our Relay variables based on user input, which triggers new queries to the server.
Now we need to associate our component to a GraphQL query; because KeyValues are simple this shouldn’t be too hard to grok:
Next up is the Relay.Route:
Remember that the GraphQL structure here is defined by the GraphQLHub server; if you want to experiment with the KeyValue GraphQL directly, check out the playground.
Finally we need to mount the Relay container:
Open the app again and search for some keys! “initialKey” is a special immutable key you can use to test:
The way we implemented our Explorer introduces some lag when typing. In a production application, you should store the input via setState instead of waiting for Relay to propagate its variables.
Before we move on to mutations, I want to mention that it’s pretty cool that we got here without writing Flux stores, actions, or manual networking code. We also get caching for free — check out the network inspector when you search the same key multiple times.
Now for the main event. Mutations in Relay have two components:
- A subclass of Relay.Mutation
- A call to Relay.Store.update with an instance of the subclass
The subclass is the bulk of our work, so let’s get to it. If ever you want more information about how the class API works, check out the Relay docs. At the top of the file, start the definition:
getMutation simply returns the GraphQL mutation we’re performing. Note that we don’t pass in any arguments into the mutation, it’s just a name. Relay will use the GraphQL introspection API to confirm that the mutation exists.
Next we need to tell Relay what to send as part of the Input (recall from earlier that the Input is the argument sent with the mutation):
The object returned from getVariables will be sent as part of the Input argument. We reference this.props, which is an object passed when creating the instance of the mutation. We’ll see where that comes from in a minute.
At this point we’ve told Relay what mutation to call and what arguments to send. The logical next piece of the puzzle is telling Relay what data we need from the server after the mutation happens:
Funky name, right? The idea is that after we mutate some data on the server, it should return all the relevant information that could have changed as a result of the mutation. For a complex application you might have to return a lot of data, but for our KeyValue example we only have to return the new key-value item.
Remember earlier we said that Relay mutations have an Input and a Payload. The GraphQL fragment returned from getFatQuery is on SetValueForKeyPayload, which accordingly is the type returned by the mutation.
The last function we need to implement is getConfigs. Naming things is hard.
In getFatQuery we told Relay the data we need from the server after a mutation; getConfigs helps Relay use that data to alter its local state.
What’s happening here is that SetValueForKeyPayload returns a field named item, which we explicitly reference as a key in the fieldIDs object. We tell Relay that the object in the payload for item should be merged with the local instance associated with the ID given by this.props.item.id. Put another way: we merge the local copy of the item with the new copy returned from the server.
There are other options for type besides FIELDS_CHANGE, such as when we want to delete or and move around items; the Relay documentation covers them.
Whew, that was fun. Now we have to trigger our mutation. First, add some UI components to our Explorer:
When you click the “Save” button, _onClick will grab the values from those inputs and fire the mutation.
Remember that the object passed in the constructor becomes the this.props we used in the class definition.
Open the app up and give it a whirl. I recommend searching for a key, setting a new value for it, and watching the UI become eventually consistent with the update (GraphQLHub is configured to have a 2-second delay on KeyValue mutations).
Eventually is the current problem with the app. We have the new value available locally, so why do we need to wait for the server to acknowledge the response?
Supporting optimistic updates is pretty simple: we just implement getOptimisticResponse in our subclass. This should return a “mock” Payload that we assume the server will return; Relay will use this Payload immediately, and eventually use the one sent from the server.
Notice that the structure of our object precisely mimics the structure of the payload we expect in getFatQuery.
If you try the app now, you’ll notice that the search results synchronize immediately with the new value.
In some cases, we want to give the user some visual feedback that the new data is still synchronizing. Relay has a nifty feature that allows us to detect this in our React components.
In our Explorer, let’s turn the text red if the new value is still saving. We use this.props.relay.hasOptimisticUpdate to do this:
Now when you save a value, we know when the server and client are not quite consistent:
If you try updating the special “initialKey” key, you’ll see the proposed new value appear in red, but the value will eventually revert to the hard-coded “initialValue”. Nifty, huh?
We’ve covered querying and mutating data with Relay. These are the building blocks of more complex applications where Relay is intended to shine.
Unfortunately there aren’t many public examples of complex Relay applications. Facebook provides a few examples bigger than the ones in these articles, but I haven’t seen anything larger from the community (leave a Note or Response if you have!).
Relay requires an upfront investment in GraphQL and some boilerplate, but the idea is that it pays dividends as your application and team scale. I’m anxious to see how the community uses it over the next year and where it goes from here.
The final source of this app is available on Github. Follow me @clayallsopp and/or @GraphQLHub for updates on more of this sort of stuff.