Skip to content

How to Create Dynamic Queries in Gatsby

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.

References