Introducing URQL (beta), a Universal React Query Library
[drinking spiked punch] What is this? Mango? -- Steve Urkel
Today we are formally releasing our newest OSS offering, urql
. Pronounced "urkel", it is technically an acronym for Universal React Query Library. urql
is a GraphQL client created in the hopes of simplifying the use of GraphQL in React.
There are some amazing solutions in the space already, notably Relay and Apollo, both of which are incredibly full-featured, brilliantly engineered, and wonderfully flexible. That said, these libraries might feel like a bit much to get started with at times, especially for beginners.
Our goal with urql
is to simplify the process of using GraphQL in React apps. There are simpler solutions, but they end up pushing the complexity of handling data storage and caching onto the user. There are also more complex solutions that allow unimagineable flexibility in a variety of library/framework contexts, but we're looking for the sweet spot that allows developers to be productive in React with GraphQL.
What it looks like
Getting started is as simple as creating a Client
instance and passing it down through your app with our Provider
:
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider, Client } from 'urql'; import Home from './home'; const client = new Client({ url: 'http://localhost:3001/graphql', }); export const App = () => ( <Provider client={client}> <Home /> </Provider> ); ReactDOM.render(<App />, document.getElementById('root'));
Next, anywhere you want to connect GraphQL to your components, you would use a Connect
component:
const MyComponent = () => ( <Connect query={query(MyQuery)} render={({ loaded, fetching, refetch, data, error, addTodo }) => { //...Your Component }} /> ); const MyQuery = ` query { todos { id text } } `;
Aside from attempts to have a simpler API, urql
also has some unique functionality of its own.
Render Props
When this project started, Apollo didn't have a render prop option, but they do now! So we also have render props, but an interesting difference is that we support providing multiple queries and mutations on the same Connect
component rather than requiring the kind of HoC nesting that you would have to do to achieve the same thing with something like Apollo.
Caching
In most of the managed GraphQL clients out there, caching is achieved by normalizing and parsing your queries and data. Full disclosure, I am not smart enough to design a good system for doing this, but beyond that, I had concerns about the performance and memory implications of this normalized intermediary, especially with large datasets. I've seen anecdotal evidence that you may also need to run a manual update because the automatic invalidation doesn't catch everything.
I set out to figure out a simpler way to cache query data. In urql
, queries are cached by stringifying an object containing both queries and their variables, then hashing it using a murmur hash, similar to what you may be familiar with seeing CSS-in-JS libraries doing with CSS rules and classnames.
The next problem was invalidation. I figured it would be better to aggressively invalidate than to miss updates, so I wanted to find a middle ground. We inject __typename
fields into queries and mutations, and keep a record of what types exist on a component connection. When mutations occur, we check our connection components to see if they contain the changed types. If they do, we invalidate their cache.
This behavior is on by default, but for a more fine-grained approach, we provide not only the ability to turn this off, but an additional prop called shouldInvalidate
that works much like React's shouldComponentUpdate
. You receive arguments that provide the changed types, the component's types, the component's data, and the mutation's response, which you can then use to decide whether or not an invalidation should occur.
We also give you the ability to provide a custom cache. If you want to cache your queries in something like AsyncStorage
, you can pretty easily hook up an adapter using our asynchronous cache callbacks. Manual fetching, fetching, invalidation, full invalidation, and cache update hooks right in the render prop are also included.
What's Next
I'm looking at this release as a public beta, where I think a lot of unsupported edge cases can be shaken out. I'm interested to see how people are using GraphQL, and where I can make urql
work better for them. I want to develop this library iteratively, making use of actionable feedback.
The biggest feature moving forward will be client side resolvers that allow you to manage your application state with GraphQL - much like you would with something like Redux, eliminating the need to include both. Imagine if you could specify a schema for your application state, including mutations and resolvers, and have it connected with the same API you use for your remote data. The next priority is to support server side rendering, which should come shortly after client resolvers.
I WANT YOUR FEEDBACK. To be honest, I don't know a ton about GraphQL. Probably more than most people, but I am by no means a power user. I would love some feedback, not only from power users, but from beginners as well. Iteratively developing a library that is powerful enough for power users, and simple enough for beginners is the ultimate goal here.
If you want to give urql
a spin, check it out here: