How to Create Dynamic Queries in Gatsby
In this article, you will learn how to dynamically set the filter that a query uses. You will change the query depending on an environment variable.
In development environment, you will retrieve all blog posts. Otherwise, you will fetch only the published ones. The posts will come from Markdown files.
Prerequisites
Read my post about querying data in Gatsby if you don’t know how to do it.
You should also understand how to use Markdown files as a data source.
Source code
You can see the full source code in this GitHub repository.
Dynamically Changing Filter in Page Components
Let’s say you want to list all posts in your IndexPage
while developing. But, in your production version, you want to show only the posts that have a isPublic
property set to true
.
You can create an object to hold your filter logic.
// gatsby-node.js
let postsFilter = {
frontmatter: {
isPublic: {
eq: true,
},
},
};
if (process.env.NODE_ENV === 'development') {
postsFilter = {};
}
To use it, you need to make your queries read their filter from a variable.
In your page component, create a query that uses a filter that comes from $myFilter
variable.
MarkdownRemarkFilterInput
is the type of filter used in allMarkdownRemark
queries.
// src/pages/index.js
const IndexPage = ({ data }) => {
const posts = data.allMarkdownRemark.nodes;
return (
<PostList posts={posts} />
);
};
export const query = graphql`
query IndexPagePosts($myFilter: MarkdownRemarkFilterInput) {
allMarkdownRemark(filter: $myFilter ) {
nodes {
id
fields {
slug
}
frontmatter {
title
date
isPublic
}
}
}
}
`;
Since Gatsby creates pages in src/pages
folder automatically, you have no control over the query variables.
You have to delete the page and recreate it. To do it, use onCreatePage
API, which captures pages that Gatsby creates. Then, you can identify the page by one of its properties, for example, path
. Finally, call the createPage
function and pass your myFilter
inside of context
property. The query inside your page will read its variables from the context
. Match the variable name with the property of context
.
// gatsby-node.js
exports.onCreatePage = ({ page, actions }) => {
const { createPage, deletePage } = actions;
if (page.path === '/') {
deletePage(page);
createPage({
...page,
context: {
...page.context,
myFilter: postsFilter,
},
});
}
};
The IndexPage
query will fetch data according to logic you provided in postsFilter
object.
Dynamically Changing Filter in createPages API
You can swap the filter in queries inside createPages
as well. You need to do, if you don’t want to create pages for the posts that are not public.
The graphql
function accepts an object as its second argument. Place your filter object inside it. The property name must match the variable name used in the query — myFilter
and $myFilter
.
// gatsby-node.js
exports.createPages = async ({ actions, graphql }) => {
const { createPage } = actions;
const result = await graphql(
`
query Posts($myFilter: MarkdownRemarkFilterInput) {
allMarkdownRemark(filter: $myFilter) {
...
}
}
`,
{
myFilter: postsFilter,
},
);
const nodes = result.data.allMarkdownRemark.nodes;
nodes.forEach(node => {
createPage({
// ...
});
});
};
Remember that you can change the logic inside postsFilter
object to whatever you to query.
Previewing Results
Run gatsby develop
to see all posts listed in IndexPage
. To see only the public posts, build your site with gatsby build
command. Run the production version with gatsby serve
. Now you can see just the public posts.
Passing Variable Filter to useStaticQuery
You cannot pass variables to useStaticQuery
at this moment. You can work around this by querying data in a page component and passing it to the regular component via props.