How GraphQL Query Passes its Response Back to Gatsby React Components

TL;DR When I started learning Gatsby, I wanted to understand the magic of GraphQL queries passing their results back to the React component. My secondary focus was to use React functional components instead of React class components as much as possible. In this blogpost, I have summarized my findings.

Most of the information comes from Gatsby Documentation and from my personal test projects.


Page Query#

Suppose we have our site metadata defined in gatsby-config.js:

module.exports = {
siteMetadata: {
title: "My Personal website",
description: "The most wonderful description of my website goes here.",
},
}

Then in the src/pages/HomePage.js (or index.js) we can write the following code, which will show the description The most wonderful description of my website goes here. on the page:

import React from 'react'
import { graphql } from 'gatsby'
const HomePage = ({data}) => {
return (
<div>
{data.site.siteMetadata.description}
</div>
)
}
export const query = graphql`
query HomePageQuery {
site {
siteMetadata {
description
}
}
}
`
export default HomePage

So, where does the HomePage function get its data from? Here we have the famous "Convention over Configuration" principle at work:

  1. the site field in the GraphQL query refers to the gatsby-config.js file above; the siteMetadata object with the description field gets exported there;
  2. the GraphQL query itself is (and should be!) also exported: export const query = graphql;
  3. as a result, the query response is passed as the data prop to the HomePage function and can be destructured as { data } from its props.

Page Query with Query Variables#

With page queries we can go a step further and add query variables to the GraphQL query. The following example shows how it can be done. It is extra interesting because it is also an example of how to query the .mdx pages in our project.

Let us look at this query in the src/templates/blog-post.js file:

export const query = graphql`
query MdxBlogPost($title: String) {
mdx(title: {eq: $title}) {
id
title
}
}
`

It uses $title as a variable and searches the MDX-nodes in the project site with the title that equals the $title string value.

For this to work,

  • the MdxBlogPost query should have gatsby-plugin-mdx plugin installed, and

  • serving of the .mdx files should be configured via gatsby-source-filesystem plugin in the gatsby-config.js file.

When a page is created dynamically from the blog post template in gatsby-node.js, you can provide an object as part of the page’s context. Keys in the context object that match up with arguments in the page query (in this case: "title"), will be used as variables. Variables are prefaced with $, so passing a title property will become$title in the query.

posts.forEach(({ node }, index) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/blog-post.js`),
// values in the context object are passed in as variables to page queries
context: {
title: node.title, // "The most wonderful description ..."
},
})
})

StaticQuery#

Gatsby v2 introduced StaticQuery, a new API that allowed not only pages but any component to retrieve data via a GraphQL query.

The table below shows the differences between page queries and the StaticQuery element.

Page QueryStaticQuery
works only in Pagesworks in any React component including Pages
accepts variables via pageContextaccepts no variables
works with React.createElementdoes not work with React.createElement, instead it is itself a JSX <StaticQuery /> element
default export: the page query itselfdefault export: an anonymous function, which returns the StaticQuery element

Simply said the StaticQuery is a JSX-element with 2 parameters:

  • query that holds a query definition, and
  • render that holds a function returning a JSX-element
<StaticQuery query={graphql`...`} render={...} />

It handles the query result in a different way than a page query. Let us consider the following code snippet:

import React from "react"
import { StaticQuery, graphql } from "gatsby"
export default () => (
<StaticQuery
query={graphql`
query HeadingQuery {
site {
siteMetadata {
title
}
}
}
`}
render={data => (
<header>
<h1>{data.site.siteMetadata.title}</h1>
</header>
)}
/>
)

How it works is that the result of the query in the query parameter automagically becomes the value of the render function data argument.

Notice the export default () => (...) signature similar to that of the page query except that here we have an anonymous default export.

From StaticQuery to useStaticQuery hook#

React hooks are becoming more and more popular, and Gatsby has created its own: useStaticQuery hook. It is based on the StaticQuery element and requires at least version 16.8.0 of React and ReactDOMpackages. Let us rewrite the previous example using this hook:

import React from "react"
import { useStaticQuery, graphql } from "gatsby"
export default () => {
const data = useStaticQuery(graphql`
query HeaderQuery {
site {
siteMetadata {
title
}
}
}
`)
return (
<header>
<h1>{data.site.siteMetadata.title}</h1>
</header>
)
}

Here, we extract data object from the useStaticQuery hook and then use it in the JSX-element we return from the default anonymous export.

Known Limitations of useStatic Query#

As Gatsby documentation states, useStaticQuery

  • does not accept variables (hence the name “static”), but can be used in any component, including pages;
  • because of how queries currently work in Gatsby, only a single instance of useStaticQuery per file is supported.

Closing Words#

We have considered three options how to use GraphQL in Gatsby:

  • Page Queries to use only on pages
  • StaticQuery to use in any JSX-component
  • useStaticQuery hook to use in any JSX-component

For every option we have shown how the query result is passed to the corresponding presentational component.