GraphQL & Complex Client State: Why We Reached For Redux

(But maybe didn’t have to)

Photo by Bennett Williamson on Unsplash

This unintentionally describes some patterns one might consider a somewhat elegant use of Redux and GraphQL together in a React app. With more headway being made in GraphQL state management recently I would probably encourage going that route. At the time, however, I am not sure we didn’t need Redux…

In April 2018 we broke ground on a client-side project intended to replace a very old ActionScript (Flash) application. (Yes, the now ancient ECMA language used in compiled .swf files).

Our application needed to talk to multiple back end services and we had to serve up the single page app itself from some a Node (or some kind of web) server so we thought GraphQL might be a good way to go.

We were pretty quickly able to implement GraphQL layer behind our client to talk to our in-house / in-domain REST services and have a framework for grabbing third-party data as needed.

The Good

We chose to use ApolloClient as the GraphQL in-memory store provider to our React app. ApolloClient is very much in active development and has come a long way since we started. It’s seriously awesome. If you’re considering GraphQL (either client or server) do yourself a favor and have a look at what the folks at Apollo are doing.

Right away we started seeing benefits without the need for any async fetching boilerplate, formerly served in a flavor of choice (thunk, epic, saga et al.). Absolutely none of that code is necessary with GraphQL. With a HOC or render prop (if you’re into that kind of thing), you write queries declaring the data your component needs and you’re done. What’s not to love?

The “first phase” feature set was super smooth. In addition to using GraphQL queries, we took to using what would be small mutations to call our downstream services for persisting to other REST services and command-y things — adding workloads to queues etc.

The Bad

During the “second phase” feature set, development was a bit tougher. For this part our data came packaged in a beast of a document. (~5k lines represented in JSON) The document contains various bits of information (very arguably separate concerns) stitched together. The contract with the service endpoint was this whole document.

Change the document you say? Not likely, the front end experience that uses this document drives $250M in revenue annually.

Each part of the document is managed by a separate UI — let’s just call them “editors”. Each editor has a unique set of inputs and controls, the common use case being the user has the ability to save their changes or back out.

Ultimately, we landed on a UX workflow that worked like so:

Crazy state management workflow using a big document store and UI editing small fragments.

At this point, we were already using Redux for things like confirmation modals and notifications from around the app — things that had nothing to do with server state. With Redux, we found modeling the above workflow to be as easy as modeling whether or not to display a notification.

Still, our supporting BFF (“Back end for front end”) end is GraphQL so when it came time to persisting the document, we now had this interesting hybrid approach which personally I thought was kind of cool. Here’s a gist example:

Redux middleware calling GraphQL mutation on certain actions

The Ugly

While the above example might seem like the best of both worlds, we had now opened the opportunity to simply jam the GraphQL client into any of our Redux actions as an acceptable pattern. Without any enforced guidelines, we quickly ended up calling queries from this layer as well.

Had we decided to keep fetching inside the redux layer from the beginning, it might have seemed a bit cleaner, but since we began strapping GraphQL queries to our components, we ended up pushing things into redux like this:

Pushing data from graphql HOC onto redux component

Takeaways

I don’t feel performing mutations in response to redux actions is that horrific but would have liked a cleaner way to couple GraphQL and Redux — or better yet have used one or the other.

I feel like we could have enforced some kind of better pattern more consistently through the app. Then again, not sure I have ever finished an app without thinking that.

Two months later…

Right before finally publishing this I decided to take a peek at https://www.apollographql.com/docs/react/essentials/local-state. Looks to me like at the very least you could re-purpose the Apollo client-side cache as a unified store with direct client writes:

“Direct writes to the cache do not require a GraphQL mutation or a resolver function”

That’s pretty awesome. There are still quite a few Redux features like middleware, years of development, solid dev tools, the store implementing observable, community size etc. but I fully expect to continue seeing more rad things from Apollo and the GraphQL community as a whole.