Why I decided not to use Relay

Maarten Schumacher
Aug 23, 2017 · 2 min read

Relay is supposed to be the glue between GraphQL and React, but just because Facebook uses it, should you?

Since I started writing React Native apps over a year ago, I’ve noticed that React takes care of a lot of complexity in the view layer. Whereas in native apps I have written complex state machines to manage the different UI states of a screen, React makes it super simple thanks to its declarative paradigm of writing UI as a function of state, abstracting away the logic that updates UI when state changes.

Instead, the complexity of a React Native app lies in what React doesn’t handle for you: data fetching and data caching. Internally, Facebook uses Relay to handle this, which sounded great to me, but since it relies on a GraphQL server, and I was stuck with REST api’s, I couldn’t use it. Recently however I found out about a great service called Graphcool which allows you to generate a GraphQL backend within minutes, so there’s no more reason not to try out Relay and see if it can abstract away all this complicated and verbose fetching and caching logic, in the same way React abstracts away view updating logic.

In the end I decided not to use Relay because the abstraction just isn’t as clean as React’s, or GraphQL’s. It’s a leaky abstraction, meaning the developer needs to know its implementation details to make it work. Such an abstraction is generally worse than no abstraction at all, since you now have to rely on implementation details of software you haven’t written yourself and have limited control over.

This leak is most apparent in how Relay handles, or rather fails to handle, updating the cache after a mutation. This example demonstrates a basic todo list app. Look at the code needed to update the list of todo’s after a new todo has been added:

updater: (store) => { 
const payload = store.getRootField(‘addTodo’);
const newEdge = payload.getLinkedRecord(‘todoEdge’);
const userProxy = store.get(user.id);
const conn = ConnectionHandler.getConnection(
userProxy,
'TodoList_todos',
);
ConnectionHandler.insertEdgeAfter(conn, newEdge);
},

(adapted from this example, don’t even look at the optimistic updater 😱)

What’s store.getRootField? payload.getLinkedRecord? What does ConnectionHandler.insertEdgeAfter do? All these methods are currently undocumented, probably because they operate directly on Relay’s internal cache, something that ideally should remain hidden from the public api.

To be fair, it’s not always obvious how a cache should be updated after a mutation. Should the new todo be added to the top of the list, or the bottom, or somewhere in-between? Maybe there is a counter state somewhere like “Todos left before it’s party-time” that needs to be incremented? It depends on the logic of your application, which means it’s hard for Relay to abstract it away.

This problem with updating the cache is symptomatic of a wrong abstraction, of a framework that tries to do too much. It doesn’t have a clearly defined problem domain, other than being the glue between two abstractions that do have one.

)

Written by

React Native developer, functional programming enthusiast

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade