In graphql, what is the best way to combine multiple sources where fields in both sources share a common value?

Viewed 349

Self-professed graphql-noob here.

That said, let us suppose the following page-level graphql query in a Mongo/Express/React/Node/Gatsby JS project:

import React from 'react';

import Layout from '../components/Layout';
import { graphql } from 'gatsby';
import { Link } from 'gatsby';
import ItemImg from '../components/ItemImg';

const IndexPage = (props) => {
  const arrayItems = props.data.allMongoDBDomainDataArraySource.edges;

  return (
    <Layout>
      <div id="main">
        <div className="inner">
          <header>
            <h1>
              Site Title
              <br />
            </h1>
            <p>
              Generic site text.
            </p>
          </header>
          <section className="tiles">
            {arrayItems.map(item =>
              <article>
                <span className="image">
                  <ItemImg></ItemImg>
                </span>
                <Link to="/Generic">
                  <h2>{item.node.propertyToBeUsed1 + ': ' + item.node.propertyToBeUsed2}</h2>
                  <div className="content">
                    <p>{'Info1: ' + item.node.propertyToBeUsed3}</p>
                    <p>{'Info2: ' + item.node.propertyToBeUsed4}</p>
                  </div>
                </Link>
              </article>,
            )}
          </section>
        </div>
      </div>
    </Layout>
  );
};

export default IndexPage;

export const pageQuery = graphql`
query DomainDataArrayAndSourceFilesystemImage{
    allMongoDBDomainDataArraySource{
      edges {
        node {
          uniqueId
          imgFilename
          metadata
        }
      }
    }  
    allFile(
      filter: {
        extension: { regex: "/(jpg)|(png)|(tif)|(tiff)|(webp)|(jpeg)/" }
        absolutePath: { regex: "/assets/images/" }
      }
    ) {
      edges {
        node {
          name
          ext
          childImageSharp {
            fluid(maxWidth: 915, quality: 70) {
              aspectRatio
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    }
  }
`

I'm hoping the above snippet is enough to get someone familiar with the underlying technologies acquainted with the context of how this query is trying to be used (for what it is worth: the above works in graphiql).

Conceptually, I've already successfully mapped the allMongoDBDomainDatayArraySource as a repeating <section> element on my index.js page.

Now, the task is: how to associate the images that have been loaded via the gatsby-source-filesystem resolver with each of the above mapped items in the <section> element as seen in the above code segment (whose each mapped item contains the filename of the image needed which can be obtained from gatsby-source-filesystem).

For clarity, allMongoDBDomainDataArraySource's imgFilename and a concatenation of allFile's edges.node.name and ext (with a '.' in between) would be the equivalent of a SQL JOIN condition that would be analogous to what I am trying to do.

I've experimented with trying to add variable arguments to the page query, but seeing as the multiple sources are combined into one query and are mapped at build time what I really would need is similar to what the JOIN operator would do in SQL.

I've also tried decomposing the <Img> Gatsby element from the index.js page and creating what is either referred to as a template or a component--which is what the ItemImg element is attempting to do.

Unfortunately, the ItemImg approach ultimately fails as page queries are incompatible with non-page components.

The only way I was able to query graphql in this context was with a StaticQuery (which works, but is not dynamic as it utilizes a hardcoded image path).

However, at least my page finally rendered appropriately (albeit, with the same image repeating 831 times).

I did find this graphql feature request regarding the ability to pass variables to StaticQuery.

Apparently, the above feature request was addressed in gatsby-plugin-image beta #27950 although it was a little unclear in the linked to discussion and my attempt at implementing ultimately failed as I ended up with the whole "the graphql query on your component will not be loaded as it attempts to use a non-page component" error again.

My goal there was to eventually pass the filename (which I have in the allMongoDBDomainDataArraySource) as a prop down to the component which would in turn query the image and return the <Img> element.

I know that some of my attempts may not be suitable for the paradigm of graphql, so I was curious if someone could help point me to at least the right concept or approach in solving this.

I've read about schema stitching, using the createPages API (which I think may be overkill for this, as I believe it is possible to combine the query in the way I am trying to achieve), and creating your own schema types (which may be what I need to do).

Let me know what you think I should research and/or implement (i.e. design patterns, etc).

1 Answers

createPages API is intended to be used when you need to generate dynamic pages or URLs based on some field (slug, URL, path, etc) so as you said, you are overkilling the process since your home page is not a dynamic path at all so this approach is not suitable for your use-case.

Said that you have multiple approaches to bypass this current limitation (as you said, is still in Gatsby's lab in beta but it's something that will take some time to be stable) but it's a broad question difficult to answer without a working sandbox to manipulate.

In my opinion, the easiest way to achieve it, if you have access to your raw data, is to manipulate your allMongoDBDomainDataArraySource to add some kind of relation to your images, some kind of field name, or something like that. In that way, once you've retrieved your allMongoDBDomainDataArraySource you can make another GraphQL query to get your images and, in the same printing <section> loop filter for that field to get the matching image. Something like:

import { Link } from 'gatsby';
import Img from 'gatsby-image';
import React from 'react';

import Layout from '../components/Layout';

const IndexPage = (props) => {
  const arrayItems = props.data.allMongoDBDomainDataArraySource.edges;
  const allImages = props.data.allFile.edges;

  return (
    <Layout>
      <div id="main">
        <div className="inner">
          <header>
            <h1>
              Site Title
              <br />
            </h1>
            <p>
              Generic site text.
            </p>
          </header>
          <section className="tiles">
            {arrayItems.map(item => {
              let matchingImage = allImages.filter(image => image.name === arrayItems.imageName); // here you are getting the proper image

              return <article key={item.node.propertyToBeUsed1}>
                <span className="image">
                  <Img fluid={matchingImage.childImageSharp.fluid} />
                </span>
                <Link to="/Generic">
                  <h2>{item.node.propertyToBeUsed1 + `: ` + item.node.propertyToBeUsed2}</h2>
                  <div className="content">
                    <p>{`Info1: ` + item.node.propertyToBeUsed3}</p>
                    <p>{`Info2: ` + item.node.propertyToBeUsed4}</p>
                  </div>
                </Link>
              </article>;
            })}
          </section>
        </div>
      </div>
    </Layout>
  );
};

export default IndexPage;

export const pageQuery = graphql`
    query DomainDataArrayAndSourceFilesystemImage{
        allMongoDBDomainDataArraySource{
            edges {
                node {
                    uniqueId
                    imgFilename
                    metadata
                }
            }
        }
        allFile(
            filter: {
                extension: { regex: "/(jpg)|(png)|(tif)|(tiff)|(webp)|(jpeg)/" }
                absolutePath: { regex: "/assets/images/" }
            }
        ) {
            edges {
                node {
                    name
                    ext
                    imageName # <--- your filter field
                    childImageSharp {
                        fluid(maxWidth: 915, quality: 70) {
                            aspectRatio
                            ...GatsbyImageSharpFluid
                        }
                    }
                }
            }
        }
    }
`;

The code's quite self-explanatory, you only need to mind the key field for your loop. The rest is just to filter (using find loop) to get the image from allImages and display it using <Img> from gatsby-image.

Related