Reuse Graphql queries in GatsbyJS and Gridsome

Gerard Lamusse
Jun 14, 2019 · 6 min read
Image for post
Image for post
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.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store