Reuse Graphql queries in GatsbyJS and Gridsome

Gerard Lamusse
Jun 14, 2019 · 6 min read
Reusable GraphQL queries in Gatsby and Gridsome

_Background

After doing a few projects in both Gridsome and Gatsby, I found that certain queries or more specifically, the same data, was being requested more than once in different components, pages and views.

__Problem

This leads to to the same data being transferred over the wire multiple times, and because queries by default aren’t reusable, GraphQL doesn’t do any lookups to see if it might already have executed a similar query.

Less requests = Faster site

A simple example where this might happen is on a blog site. On the front page you might be displaying a list of the recent articles in a grid view, but you would also like to display those same articles on each article page but as a slider. Sure the one view might include an extra field eg. published_date or two, but rather merge the two queries into one and use the same query for both places.

Yes and no, you probably use them within a component with a set design which you then use multiple places, eg. Social Share Icons.

But where this solution will come in handy is where you have different designs, views or even additional data that you want to use with this query component.

___Solution

Wrap each query that you plan on reusing in its’ own component that will contain a single slot that you can use to expose the results of the query.

Let’s do this step by step.

  1. Create a folder that will contain all the query components. eg. queries
  2. Create a folder that will contain all the query components. eg. queries
  3. Within create a new file, prefixing with Q eg. QFeaturedPosts.jsx
    The reason for the prefix is just a way we can distinguish these components from other React components.
  4. Within the file we’ll add the following:
// QFeaturedPosts.jsximport React from 'react';
import { StaticQuery, graphql } from 'gatsby';
export default ({ children }) => (
<StaticQuery query={query}
render={({ posts }) => {
return children(posts && posts.edges.map(e => e.node))
}}
/>
)
const query = graphql`
query QFeaturedPosts {
posts: allPosts(limit:8, filter: { featured: {eq: true}}) {
edges {
node {
id
title
excerpt
date(formatString: "MMMM Do, YYYY")
path
image {
source_url
}
}
}
}
}
`

The query component expects children to be a function that it will call with the data it has. You can transform the data anyway you want before passing it to children.

In typescript the function looks like this:

// QFeaturedPosts.tsxinterface QProps {
children: (data: Boolean | any[]) => React.ReactNode
}
export default ({ children }: QProps) => (
<StaticQuery query={query}
render={({ posts }) => {
return children(posts &&
posts.edges.map((e: { node: any }) => e.node))
}}
/>
)

5. Now to use this query component we import it where needed and make use of the data past through it. eg:

import QFeaturedPosts from '@/queries/QFeaturedPosts';export default () => {
return (
<Sidebar>
<QFeaturedPosts>{
(featuredPosts) => <Posts posts={featuredPosts} />
}</QFeaturedPosts>
</Sidebar>
)
}

We can even stack query components now for even more power

import QFeaturedPosts from '@/queries/QFeaturedPosts';
import QPopularPosts from '@/queries/QPopularPosts';
export default () => {
return (
<Sidebar>
<QFeaturedPosts>{
(featuredPosts) => (
<Posts posts={featuredPosts} />
)
}</QFeaturedPosts>
</Sidebar>
)
}
  1. Create a folder that will contain all the query components. eg. queries
  2. Within create a new file, prefixing with Q eg. QFeaturedPosts.vue
    The reason for the prefix is just a way we can distinguish these components from other Vue components.
  3. Within the file we’ll add the following:
// QFeaturedPosts.vue<template>
<span>
<slot v-bind:posts=”posts”></slot>
</span>
</template>
<script>
export default {
computed: {
posts() {
return this.$static.posts &&
this.$static.posts.edges.map(e => e.node)
}
}
}
</script>
<static-query>
query FeaturedPosts {
posts: allPosts(limit: 8, filter: { featured: { eq: true }}) {
edges {
node {
id
title
excerpt
date(format: 'dd-mm-yyy')
path
image (width: 200, height: 200)
}
}
}
}
</static-query>

The template contains a slot that will expose the data we want from the query.

The script with the computed property can be used to map or transform the query results before passing the data further.

The query itself has to just be a static query fetching the data you need.

4. Now to use this query component we import it where needed and make use of the data past through it. eg:

<template>
<aside class=”side-menu__right”>
<QFeaturedPosts v-slot=”{ posts: featured_posts }”>
<h3>Featured Posts</h3>
<template v-for=”fpost in featured_posts”>
<g-link :key=”fpost.id” :to=”fpost.path”>
<g-image :src=”fpost.image” width="200" height="100"/>
<h4>{{fpost.title}}</h4>
<p>{{fpost.excerpt}}</p>
</g-link>
</template>
</QFeaturedPosts>
</aside>
</template>
<script>
export default {
components: {
QFeaturedPosts: () => import(‘../queries/QFeaturedPosts.vue’)
}
}
</script>

You can stack multiple query components within each other as well. In the following example we get featured and popular posts and merge them into one array to then display.

<template>
<aside class=”side-menu__right”>
<QFeaturedPosts v-slot=”{ posts: featured_posts }”>
<QPopularPosts v-slot=”{ posts: popular_posts }”>
<h3>Posts</h3>
<template v-for=”post in [...featured_posts, ...popular_posts]”>
<g-link :key=”post.id” :to=”post.path”>
<g-image :src=”post.image” width="200" height="100"/>
<h4>{{post.title}}</h4>
<p>{{post.excerpt}}</p>
</g-link>
</template>
</QPopularPosts>
</QFeaturedPosts>
</aside>
</template>
<script>
export default {
components: {
QFeaturedPosts: () => import(‘../queries/QFeaturedPosts.vue’),
QPopularPosts: () => import(‘../queries/QPopularPosts.vue’)
}
}
</script>

This is a very simple example of how it can be used. You can now reuse the QFeaturedPosts component in other places for example the footer or front page.

It is important to note that if the component itself looks the same everywhere you use it then you are better of adding the html and styles to the QFeaturedPosts component as to not have to repeat yourself. However if the places you use the data will look different then this method really helps.

Stacking

Though stacking query components within each other is already powerful in its own right it does lead to messy stacky code. Another option you have to manually create an instance of the component and attaching the data you need to your component.

In Vue (what Gridsome uses) this is very simple, lets take the example from above:

<template>
<aside class=”side-menu__right”>
<h3>Posts</h3>
<template v-for=”post in [...featured_posts, ...popular_posts]”>
<g-link :key=”post.id” :to=”post.path”>
<g-image :src=”post.image” width="200" height="100"/>
<h4>{{post.title}}</h4>
<p>{{post.excerpt}}</p>
</g-link>
</template>
</aside>
</template>
<script>
import Vue from 'vue'
import QFeaturedPosts from '../queries/QFeaturedPosts.vue'
import QPopularPosts from '../queries/QPopularPosts.vue'
export default {
data() {
return {
qfp: null,
qpp: null
}
},
mounted() {
// Instantiate the Query Components
const QFP = Vue.extend(QFeaturedPosts)
this.qfp = new QFP()
this.qfp.$mount()
const QPP = Vue.extend(QPopularPosts)
this.qpp = new QPP()
this.qpp.$mount()
},
computed: {
// Using computed variables makes it easy to watch and get data
featured_posts() {
return this.qfp && this.qfp.posts
},
popular_posts() {
return this.qfp && this.qfp.posts
}
}
}
</script>

Now what

With this structure in place it is possible to only need to fetch data for a specific object once instead of having fetching the same info again and again.

So if you already have some information ( name, image, author, price ) about a product or blog article because you display them in a list on the front page, then when you actually visit the product or article page you only load the extra information ( body, gallery, tags ) that you still need.

Remarks

I’ll admit I am not 100% sure about this entire setup since you are including an extra component per Query that you want to encapsulate, but what I am sure about is that I feel better about a project where I know I am not fetching duplicate data in one form or another.

Let me know in the comments what you think.

DAY4

The beginning is always today.

Gerard Lamusse

Written by

DAY4

DAY4

The beginning is always today.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade