styler
styler

Reputation: 16481

How to reduce static query code duplication Gatsby JS

I've created a React static query hook that requests site and page level meta data. At the moment I'm duplicating this code on every page build as 1 part of the query changes from page to page allProductsJson, allProductJson etc etc. I was wondering if it is possible to remove this duplication by using the querying capabilities more efficiently? I've read that Gatsby static queries don't support arguments, but maybe there's a way to query all meta and filter by page?

Example of a duplicated query hook:

import { graphql, useStaticQuery } from 'gatsby';
import pageMetadataSelector from 'utils/page-metadata-selector';
import siteMetadataSelector from 'utils/site-metadata-selector';
import getAbsoluteUrl from 'utils/get-absolute-url-path';

const QUERY = graphql`
  {
    pageMetadata: allProductsJson {
      nodes {
        metadata {
          title
          description
          image {
            publicURL
          }
          robots
        }
      }
    }
    site {
      siteMetadata {
        siteUrl
      }
    }
  }
`;

const usePageMetadataQuery = ({ location }) => {
  const { pageMetadata, site } = useStaticQuery(QUERY);
  const pageData = pageMetadataSelector(pageMetadata);
  const { siteUrl } = siteMetadataSelector(site);

  return {
    metadata: {
      ...pageData,
      siteUrl: getAbsoluteUrl(siteUrl, location.pathname),
      image: getAbsoluteUrl(siteUrl, pageData.image.publicURL)
    }
  };
};

export default usePageMetadataQuery;

Upvotes: 0

Views: 691

Answers (1)

coreyward
coreyward

Reputation: 80041

Given what you’re doing here, I think you would be better served by adding the relevant data to the page context in gatsby-node.js (in the createPage call), then using wrapRootElement (in gatsby-browser.js and gatsby-ssr.js) to extract those details from the page context.

From there you can either render the component you need directly or pass it to a React Context Provider for consumption by any children.

Here’s an example showing the latter:

export const wrapPageElement = ({ element, props: { context: { metadata} } }) => {
  return (
    <MetadataContext.Provider value={metadata}>
      {element}
    </MetadataContext.Provider>
  )
}

It’s also possible to fetch this data in each of your page queries and access it in the same way (destructure data instead of context):

exports.createPages = async ({ graphql, actions }) => {
  const {
    data: {
      pageMetadata,
      pageData,
    }
  } = await graphql(`
    {
      pageMetadata: allProductsJson {
        nodes {
          slug
          metadata {
            title
            description
            image {
              publicURL
            }
            robots
          }
        }
      }

      pageData: allPageJson {
        pages: {
          slug
        }
      }
    }
  `

  // for each page
  pageData.pages.map(({ slug }) => {
    // select the metadata with the matching slug
    const { metadata } = pageMetadata.nodes.find(node => node.slug === slug)
    
    // create the page
    actions.createPage({
      path: `/${slug}/`,
      component: `./src/templates/page.jsx`,
      context: { 
        // pass both the slug and metadata as context
        slug,
        metadata,
      }
    })
  })
}

Upvotes: 1

Related Questions