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

  1. Click on the following button to deploy GraphQL engine on Hasura Cloud including Postgres add-on or using an existing Postgres database:

    Deploy to Hasura Cloud

  2. Open the Hasura console

    Click on the button "Launch console" to open the Hasura console.

  3. Create table users

    Head to the Data tab and create a new table called users with columns:

    • id (text)
    • name (text)
    • created_at (timestamp now()).
  4. 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.

Blog
14 Dec, 2020
Email
Subscribe to stay up-to-date on all things Hasura. One newsletter, once a month.
Loading...
v3-pattern
Accelerate development and data access with radically reduced complexity.