Beyond Static, a Gatsby Tale
Tags: react, gatsby, graphql, headlesscms
Published: 2020-01-29 14:36:17

TLDR;
Here's the important stuff.

{% twitter 1220379534928662528 %}

Follow me for ~~my personal vanity~~ engaging content about building software and stories about my kids.

## Intro

In the world of static site generators, one rule remains constant - the build is the gospel. Whatever was at build time will be until a new build occurs. It's part of what makes them so fast, anything sourced from a database, flat-file, CMS, or what have you - becomes stamped into code in HTML, CSS, and JavaScript. Once that transformation occurs, there's no need for data fetching, transformation or template rendering - that part is done! Browsers can simply show you exactly what the server sends.

But sometimes our data, you know, changes. Imagine running a stock exchange on a static site?! Even modern ecommerce sites have pricing that can vary hundreds of times a day to reflect real-time ~~price manipulation~~ supply and demand forces.

So what's a JAMing developer to do? Well, the obvious solution is to build the parts that have a longer "TTL (time-to-live)" and fetch the changing bits from the client.

When dealing with Gatsby, however, that presents a challenge. One of my favorite things about Gatsby is the content mesh API that it creates. You can throw nearly any data source at it, even plain text files, and someone in the community will have created a plugin that parses the data and puts it into a flexible GraphQL API. From there, you can query all the data you want, and push the data through React templates. It's really a fantastic experience.

But it only works at build time. The API goes away, the data is stamped to a persistent state for rehydration, the clowns get back in the car and go home. If you want to query data from the browser, you'll need to reference back to the original data source that the Gatsby plugin you are using is sourcing from. Most external systems are still exposing a REST interface which means you now need to work with two different API protocols.

## A Hero emerges

Thankfully, a growing number of online services are beginning to expose a native GraphQL interface, too! We at [GraphCMS](https://graphcms.com) have been native GraphQL from the beginning, and when you source content from us, you can use the same knowledge base and experience you've gathered building the static site to now fetch content straight from the original source.

Let's look at the two places we fetch data in our demo example. Our domain, for context, is a hotel listing website, cleverly named "Gotell" that fetches available rooms dynamically.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/hcj2trfb43cthtl11yo1.png)


I'm available for naming your Startup if you need me.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6q7oj8egpamlampbdc5j.jpg)

To generate this index page, we fetch our data in the `gatsby-node.js` file. As I generated multiple demos for this talk, you'll notice I fetch TWO batches of data and merge them together, this is not needed in most cases but I chose to do that since I used the separate data sources for other demos.

```js
exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions

  // import various templates needed...


  const minimalQuery = await graphql(
    `
      query {
        gcms {
          hotels {
            id
            slug
            name
          }
        }
      }
    `
  )

  if (minimalQuery.errors) {
    throw result.errors
  }

  const minHotels = minimalQuery.data.gcms.hotels

  const extendedQuery = await graphql(
    `
      query {
        gcms {
          hotels {
            id
            description
            photos {
              url
            }
          }
        }
      }
    `
  )

  if (extendedQuery.errors) {
    throw result.errors
  }

  const extendedHotels = extendedQuery.data.gcms.hotels

  // Create a merged data set, what would essentially be one large query"
  const hotels = merge(minHotels, extendedHotels)

  /*
   Demo One!
   Creating a single large index from the content
  */

  createPage({
    path: "/demo-one/hotels",
    component: hotelIndexPage,
    context: {
      hotels,
    },
  })

...

})
```

We pass that list of hotels to the `pageContext` where it will be converted to HTML at **build** time. From the template, we fetch our dynamic content.

```jsx
const Hotels = ({ pageContext }) => {
  const [hotels, updateHotels] = useState(pageContext.hotels)
  const [roomsFetched, setRoomsFetched] = useState(false)

  useEffect(() => {
    let isCurrent = true
    ;(async () => {
      const { data } = await postData(process.env.GATSBY_GCMS_URL, {
        query: `query {
            hotels {
              id
              rooms
            }
          }`,
      })
      if (isCurrent) {
        updateHotels(hs => merge(hs, data.hotels))
        setRoomsFetched(true)
      }
    })()
    return () => (isCurrent = false)
  }, [])

  return (
    <Layout>
      <SEO title="Demo One" />
      <div className="flex flex-wrap">
        {hotels.map((hotel, key) => (
          <HotelBox hotel={hotel} key={key} roomsFetched={roomsFetched} />
        ))}
      </div>
    </Layout>
  )
}
```

An important detail here is that we push the `pageContext` data into the React state and iterate over that array instead of directly from pageContext. This allows us to update our state with the fetched data from our [`useEffect` hook](https://overreacted.io/a-complete-guide-to-useeffect/) and update the entries where applicable.

## Summary

There's nothing really more complex about this example. The basic process follows fetching data at build time, then fetching partial data at client load time and using an updater pattern to let us combine the data on the page. The key benefit here is being able to use GraphQl for both parts. If you examine the two code samples above we are writing nearly identical query syntax in both cases (our `gatsby-source-graphql` plugin adds a new top-level type which introduces an extra level of nesting for the build-time query.)

Removing the cognitively expensive context switching from any other API source to the data-mesh GraphQL API is a major win. The only catch is that you need a system that natively supports GraphQL - [which is something we are happy to help you with!](https://graphcms.com)