GraphQL Data Fetching with SWR React Hooks and Hasura
From SWR's github repo:
The name “SWR” is derived from stale-while-revalidate
, a cache invalidation strategy popularized by HTTP RFC 5861. SWR first returns the data from cache (stale), then sends the fetch request (revalidate), and finally comes with the up-to-date data again.
GraphQL requests are just HTTP POST requests with a JSON payload at the end of the day. It doesn't need the typical setup overhead for data fetching. Popular GraphQL clients have made it look slightly complicated than a normal fetch request counterpart, but for added benefits like caching.
The SWR API is simple and looks like the one below:
const { data, error, isValidating, mutate } = useSWR(key, fetcher, options)
We will focus on the arguments key
and fetcher
. In a GraphQL API implementation with SWR, the GraphQL document will be the key that will be used. In REST APIs, the URL endpoint will usually be the key (something like /api/users
. The fetcher
function is an async function that returns the data. It gets the key and the logic of data fetching is written inside this. We will split our logic between HTTP POST requests and native websocket connections here.
The unique key ensures that requests are not sent multiple times if used in different components in the same page.
We can use simple data fetching libraries like graphqurl
(read about Graphqurl 1.0) or graphql-request
to send a GraphQL query. In the example below, I'm sticking to native fetch
library for making the HTTP POST API calls and native WebSocket
client.
The idea behind this example is to demonstrate usage of useSWR
, mutate
and trigger
hooks of SWR
in combination with GraphQL. We need a GraphQL API and we are going to make use of Hasura Cloud to get it instantly.
Deploy Hasura to get a GraphQL API
-
Click on the following button to deploy GraphQL engine on Hasura Cloud including Postgres add-on or using an existing Postgres database:
-
Open the Hasura console
Click on the button "Launch console" to open the Hasura console.
-
Create table users
Head to the
Data
tab and create a new table calledusers
with columns:- id (text)
- name (text)
- created_at (timestamp now()).
-
Try out a GraphQL Query
query { users { id name created_at } }
We are going to make use of the above users
query inside our react frontend.
Clone the swr-graphql
repo and head to the libs
directory that contains the wrapper for both fetch and websockets. Change the GraphQL endpoint to point to the Hasura Cloud project URL.
GraphQL Query
The useSWR
hook gets a graphql query as the key and the fetcher function is defined in getData
. We have written a custom fetch
method in the libs directory.
import useSWR from 'swr'
const query = {
'query': 'query { users(limit:10, order_by:{created_at: desc}) { id name } }'
};
const getData = async(...args) => {
return await fetch(query);
};
export default () => {
const { data, error } = useSWR(query, getData);
if(error) {
return <div>Error...</div>
}
if(!data) {
return <div>Loading...</div>
}
}
The hook returns the data / error and the respective states can be handled on the rendering logic.
GraphQL Mutation
Once a mutation is done, you would want to update existing state by modifying data (addition / removals / updates). This is possible via the mutate
method, where a key
can be passed (GraphQL query will be the key for us) and the new data to be used along with a boolean to specify whether you want to revalidate or not.
export default () => {
const [text, setText] = React.useState('');
const { data } = useSWR(query, getData)
async function handleSubmit(event) {
event.preventDefault()
// mutate current data to optimistically update the UI
mutate(query, {users: [...data.users, {id: uuidv4(), name: text}]}, false)
// send text to the API
const mutation = {
'query': 'mutation users($name: String!) { insert_users(objects: [{name: $name}]) { affected_rows } }',
'variables': { name: text}
};
await fetch(mutation);
// revalidate
trigger(mutation);
setText('')
}
return ...
}
Once the mutation is complete, we revalidate it with a trigger.
GraphQL Subscriptions
Finally for subscriptions with realtime data, we have a custom wrapper defined in libs. Using the native WebSocket
instance, we are creating an initial message that will be sent to the GraphQL server (containing the payload like headers).
const ws = new WebSocket(GRAPHQL_ENDPOINT_WS, "graphql-ws");
const init_msg = {
type: 'connection_init',
payload: { headers: headers }
};
There's no native Subscription hook with SWR, but we will reuse useSWR
to call a fetcher that initiates a new WebSocket connection.
Pre-rendering
All of the above examples assumes that data loading happens on the client side. But if you are building an app which requires pre-rendering for SEO/Caching benefits, then you can pass the pre-fetched data as the initial value to the initialData
option.
For example:
const { data, error } = useSWR(query, getData, { initialData: props.users } );
This combined with getStaticProps
will take care of the pre-rendering of data at the server side.
Source Code - https://github.com/praveenweb/swr-graphql
Do try it out and let us know in the comments on what frontend library you prefer using for data fetching via GraphQL.