Relay Notes ●
GraphQL is new way of fetching data from server championed at Facebook. It’s a spec, mostly straightforward, which needs to be followed both at server and client to achieve its goals of a schema backed data correspondence.
Relay is an extension of GraphQL which is an attempt to bring sanity in other practical aspects of data management at the client especially for React based applications. However, being a spec it’s not a React only thing.
I am attempting to port a fully semantic model based web application with mildly complex user interface and rich with REST & adhoc endpoints to GraphQL-Relay-React land.
As I mentioned before, GraphQL as a spec and Facebook’s reference implementation is pretty straightforward. Moreover, as my backend is nothing but bunch of RDFs stored in a triple store backed by an OWL based semantic model, creating a GraphQL schema for the client was not that challenging.
The real challenge comes when Relay comes into picture.
Relay tries to simplify some practical pain points of front-end development like data flow between UI components, state / scope management , both local and global, separation of concern while fetching data from server, managing timeouts and retries. And for that it takes the convention over configuration approach. By design, it’s built with React in mind (but there is no reason why it can’t be used without it).
GraphQL has the term query as part of its name and for a good reason. As long as the application is asking for data from server, everything works fine. But to have any meaningful implementation, we need write some data. Or as they say in functional world, we need to have side effects. In GraphQL land, its called Mutations.
Mutations are a late addition to the spec and again simple enough. It just mandates client to say along with what they want to write to also mention how is that gonna change the overall system. I.e. there is
payload object for every mutation request which clients should leverage to ask GraphQL server to fetch everything that that they think might have changed because of what they have done. At a client level it’s somewhat similar to a Monadic system (without the required laws though) where the overall state is also part of the input & output.
Relay’s take on mutations is still a bit rusty. Both at the spec level as well as in the current implementation. Having said that, I am pretty confident on the job the committers at Facebook and others in the open are doing.
The documentation around mutations in Relay is pretty weak. Other than this video and some posts explaining simple scenarios, the official documentation and source code of these examples as part of relay framework is the best we’ve got right now.
There are some important discussions around mutations is happening as Github issues and StackOverflow questions.
I just want to reiterate, Relay (more than GraphQL) needs a different way of thinking in building client-server applications. I will surely have more to write on it (in much smaller doses than this one) as I tread deep into this land.
Here are few of my realisations for now
Object Identification is an important concept when working with Relay-on-React(RoR). In almost all of the examples of Relay, the entry to the application happens by fetching a collection of a particular type which means that Relay store gets to know about the IDs of the individual instances.
These IDs as mentioned in the documentation are opaque which in other words is the domain agnostic way for both client and server to identify an instance. It’s synonymous to ID column in databases (SQL & NOSQL both) which is different from the domain specific identification of the same instance.
Relay Store saves those IDs as
__dataID__for each instance that it tracks and then use the same for all it’s data fetching exercise like when some React component would want to get additional information of those instance or the instance changes state as a result of some mutation.
Unless the ID of an instance remains consistent to what Relay saves and what server sends, there will be weird warning / error messages from Relay. Or Relay won’t bother creating the right query to GraphQL server. Which is more frustrating as I spent a lot of time debugging my application rather than looking at the request created by Relay.
A way that worked for me and I think something that is recommended when thinking in Relay is that
your instances are always expected be treated with an unique ID that follows a pattern and is different of from the domain specific identifier (if any)
the pattern is known to server so that it can be deciphered in the resolve function of your
Nodedefinition and the domain specific ID is used for further processing
Relay client should be unaware of the pattern so that the separation of concern remains intact
the same id, however, should be used whenever Relay client need to identify the instance e.g. in
Mutationclass or a React component creating query
fragmentsfor relevant information of the same instance
Relay store internally maps the
idfield of the
Nodetype (Relay specific interface for your GraphQL schema) to
__dataID__and then use the same to track the same instance across the application boundaries. Our app need not concern with the internal representation but be very sure that the
idof our instance is consistent & available to Relay store at all times. Best way to check the same is via React Chrome Plugin where we can see what
propsdata Relay containers are having.
Mutation apis at client side is still developing and some major updates are coming in future releases of relay. One confusing feature is the
RANGE_ADDconfiguration for the Relay mutator
If as a result of a mutation, some new
edgesare added to an existing
connection(Relay’s way to represent the graph of associations between GraphQL types e.g. a
usertype has a connection
todosto represent list of
todotypes), the mutation should have a
connectionEdgeas one of the output fields
the same edge then can be used in the
getConfigsfunction of the Mutation to ask Relay store to magically take care of replicating the same addition operation in it’s store mimicking what happened at the server side. It’s a really powerful feature but still has some rough edges especially when it throws weird warnings and fail silently. So here is what worked for me
RANGE_ADDoption for the mutator will only work for instances that are already getting tracked by Relay. In other words, if the
connectionin question is not queried before by any component then Relay is going to skip asking for it when it frames the final mutation query and show this weird warning -
newly created edge <edge_name> and its <node_name> field. Did you forget to update the RANGE_ADD mutation config?.
Relay only looks for common fields when it does intersection between what’s asked in the
getFatQueryand what exactly it’s components need as per their
getFragmentsdefinition. So, check that the connection is at least used in one of the component’s
in future updates the above warning message will be replaced with relevant message based on whether the problem is at the client end (i.e. when connection is never queried before being asked as part of the mutation) or at the server side when server is not including the new edge as part of the mutation payload. Check out issue #542 for the discussion and issue #574 for a possible implementation
in cases where you still need to get fields in the mutation payload which are not being asked by any component, there is a new option
REQUIRED_CHILDRENwhich would force Relay to include the relevant fields in its mutation query. Check out issue #237