skip to Main Content

I’m trying to create a Figure component in which I pass the img src along with other data.

I realize it’s not straightforward—e.g., this thread—but I thought it would work fine with normal HTML img tags.

The image is not displaying. Does that mean that this limitation also applies to HTML img tags within components?

If this is the case, I guess I indeed ought to use Gatsby’s dynamic images. How would I do this in a static query (nonpage component)? This thread had me believing it isn’t possible—or at least a hack?

The component inside MDX documents:

<Figure 
  image=""
  size=""
  caption=""
  credits=""
/>

Figure.js:

import * as React from "react"

const Figure = ({ image, size, caption, credits }) => {
  return (
    <figure className={size}>
      <img src={image} alt={caption} />
      <figcaption>
        <span>{caption}</span>
        <span>{credits}</span>
      </figcaption>
    </figure>
  )
}

export default Figure

articlePostTemplate.js

import * as React from "react"
import { graphql } from "gatsby"
import { MDXRenderer } from "gatsby-plugin-mdx"
import Layout from "../components/layout.js"
import Seo from "../components/seo.js"

const PostTemplate = ({ data, location }) => {
  let post = data.mdx

  return (
    <Layout location={location}>
      <Seo
        title={post.frontmatter.title}
        description={post.frontmatter.lead}
        date={post.frontmatter.computerDate}
      />
      <article className="post">
        <header className="post" id="intro">
          <p className="date">
            <time dateTime={post.frontmatter.computerDate}>{post.frontmatter.humanDate}</time>
          </p>
          <h1 itemprop="headline">{post.frontmatter.title}</h1>
          <p className="lead">{post.frontmatter.lead}</p>
        </header>
        <section className="post" id="body-text">
          <MDXRenderer data={data}>{post.body}</MDXRenderer>
        </section>
      </article>
    </Layout>
  )
}

export default PostTemplate

export const pageQuery = graphql`
  query PostBySlug(
    $id: String!
  ) {
    site {
      siteMetadata {
        title
      }
    }
    mdx(id: { eq: $id }) {
      id
      excerpt(pruneLength: 160)
      body
      frontmatter {
        title
        computerDate: date(formatString: "YYYY-MM-DD")
        humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
        hook
        type
        lead
        featuredImage {
          childImageSharp {
            fluid(maxWidth: 800) {
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    }
  }
`

gatsby-config.js

module.exports = {
  …
  },
  plugins: [
    `gatsby-plugin-image`,
    `gatsby-plugin-sitemap`,
    {
      resolve: `gatsby-plugin-mdx`,
      options: {
        extensions: [`.md`, `.mdx`],
        gatsbyRemarkPlugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: `900000000000`,
              linkImagesToOriginal: false,
              backgroundColor: `none`,
            },
          },
          {
            resolve: `gatsby-remark-responsive-iframe`,
            options: {
              wrapperStyle: `margin-bottom: 1.07var(--line-length)`,
            },
          },
          `gatsby-remark-prismjs`,
          `gatsby-remark-copy-linked-files`,
          `gatsby-remark-smartypants`,
          {
            resolve: `gatsby-remark-autolink-headers`,
              options: {
                icon: false,
                itemprop: `heading`,
                maintainCase: false,
                removeAccents: true,
                elements: [`h2`, `h3`, `h4`],
              },
          }
        ],
      },
    },
    …
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `content`,
        path: `${__dirname}/content/`,
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/data`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/pages/`,
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `journalistikk`,
        path: `${__dirname}/content/journalism/`,
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `discussion`,
        path: `${__dirname}/content/discussion/`,
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `photography`,
        path: `${__dirname}/content/photography/`,
      }
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Gatsby Starter Blog`,
        short_name: `GatsbyJS`,
        start_url: `/`,
        background_color: `#ffffff`,
        display: `minimal-ui`,
        icon: `src/images/gatsby-icon.png`,
      },
    },
    `gatsby-plugin-react-helmet`,
  ],
}

gatsby-node.js

const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions

  const articlePostTemplate = path.resolve(`./src/templates/articlePostTemplate.js`)

  const result = await graphql(
    `
      {
        allMdx(
          sort: { fields: [frontmatter___date], order: ASC }
          limit: 1000
        ) {
          nodes {
            id
            frontmatter {
              title
              computerDate: date(formatString: "YYYY-MM-DD")
              humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
            }
            fields {
              slug
            }
          }
        }
      }
    `
  )

  if (result.errors) {
    reporter.panicOnBuild(
      `There was an error loading your blog posts`,
      result.errors
    )
    return
  }

  const posts = result.data.allMdx.nodes

  if (posts.length > 0) {
    posts.forEach((post, index) => {
      [index + 1].id

      createPage({
        path: post.fields.slug,
        component: articlePostTemplate,
        context: {
          id: post.id
        },
      })
    })
  }
}

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `Mdx`) {
    const value = createFilePath({ node, getNode })

    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

exports.createSchemaCustomization = ({ actions }) => {
  const { createTypes } = actions

 
  createTypes(`
    type SiteSiteMetadata {
      author: Author
      siteUrl: String
      social: Social
    }

    type Author {
      name: String
      summary: String
    }

    type Social {
      twitter: String
      instagram: String
      mail: String
    }

    type MarkdownRemark implements Node {
      frontmatter: Frontmatter
      fields: Fields
    }

    type Frontmatter {
      title: String
      description:  String
      date: Date @dateformat
    }

    type Fields {
      slug: String
    }
  `)
}

layout.js

import * as React from "react"
import { MDXProvider } from "@mdx-js/react"
import { Link } from "gatsby"
import DWChart from "react-datawrapper-chart"
import Header from "./Header"
import Footer from "./Footer"
import Figure from "./Figure"

const shortcodes = { Link, DWChart, Figure }

export default function Layout({ children }) {
  return (
    <div className="layout-wrapper">
      <Header />
      <main>
        <MDXProvider components={shortcodes}>{children}</MDXProvider>
      </main>
      <Footer />
    </div>
  )
}

2

Answers


  1. Chosen as BEST ANSWER

    Hat tip to Ferran for helpful guidance.

    After more research, I revised my solution—

    articleTemplate.js

    /* shortcodes */
    
    const ArticleTemplate = ({ data, location }) => {
      let post = data.mdx
    
      return (
        <Layout location={location}>
          <Seo
            title={post.frontmatter.title}
            description={post.frontmatter.lead}
            date={post.frontmatter.computerDate}
          />
          <article className="article">
            <p className="date">
              <time dateTime={post.frontmatter.computerDate}>{post.frontmatter.humanDate}</time>
            </p>
            <h1 itemprop="headline">{post.frontmatter.title}</h1>
            <p className="lead" itemprop="introduction">{post.frontmatter.lead}</p>
            <MDXProvider components={shortcodes}>
              <MDXRenderer
                data={post.frontmatter.thumbnail}
                localImages={post.frontmatter.embeddedImagesLocal}
              >
                {post.body}
              </MDXRenderer>
            </MDXProvider>
          </article>
        </Layout>
      )
    }
    
    export default ArticleTemplate
    
    export const pageQuery = graphql`
      query ArticleBySlug($id: String!) {
        site {
          siteMetadata {
            title
          }
        }
        mdx(id: {eq: $id}) {
          id
          excerpt(pruneLength: 160)
          body
          frontmatter {
            title
            computerDate: date(formatString: "YYYY-MM-DD")
            humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
            hook
            type
            lead
            thumbnail {
              childImageSharp {
                gatsbyImageData(
                  layout: FULL_WIDTH
                )
              }
            }
            embeddedImagesLocal {
              childImageSharp {
                gatsbyImageData
              }
            }
          }
        }
      }
    `
    

    figure.js

    import * as React from "react"
    import { GatsbyImage, getImage } from 'gatsby-plugin-image'
    
    const Figure = ({ source, size, caption, credit }) => {
      return (
        <figure className={size}>
          <GatsbyImage image={getImage(source)} alt={caption} />
          <figcaption>
            <span>{caption}</span>
            <span>{credit}</span>
          </figcaption>
        </figure>
      );
    }
    
    export default Figure
    

    index.mdx

    ---
    …
    thumbnail: "thumb.jpeg"
    embeddedImagesLocal:
      - "first.jpeg"
      - "second.jpeg"
    ---
    
    <Figure
      source={(props.localImages [0])} <!-- first image; second image would be `[1]` -->
      size="I'm a `className`"
      caption="I'm a caption"
      photographer="I'm a name."
    />
    

    (Marking this as the solution as it's the most helpful for anyone looking to do this in the future. It also shows how to query for embedded images and featured images—at the same time.)


  2. The limitation you mention in How to pass a path of image as a prop in Gatsby in Gatsby-plugin-image only applies to StaticImage + dynamic props data (with a dynamic source), meaning that the component that returns a StaticImage cannot receive the src as a props, because all the props of StaticImage needs to be statically analyzed.

    In other words, your MDX can receive a src to be used in a <img> tag or you can use GatsbyImage component if properly configured.

    Keep in mind that the query that will fetch GatsbyImage data (childImageSharp, gatsbyImageData, etc) must be placed in a top-level component (pages) if using a page query or in a useStaticQuery hook if used elsewhere.

    The approach in both scenarios (using <img> or GatsbyImage) is similar. You need to:

    • Provide your image sources in your markdown file

    • Query those images using a page query or useStaticQuery to provide to your MDXProvider the queried image props. Using the blog example:

        <MDXProvider>
          <MDXRenderer data={data}/>
        </MDXProvider>
      

      data stands for your queried data in a GraphQL page query or useStaticQuery. Without knowing your data structure I haven’t add the GraphQL part because I do not know the nodes available.

    • At this point, your MDXProvider holds the images data (all from your markdown files), you just need to provide it to your GatsbyImage or Figure component:

      // some.mdx 
      
      import Figure from 'path/to/figure/component';
      
      ## Post Body
      
      Lorem ipsum dolor...
      
      <Figure img={props.data} />
      

      As you can see, I lift all props to Figure just to allow to debug (using console.logs() for example) to see the available props there like:

      const Figure = (props) => {
         console.log(props);
         return (
           <div>I will be a figure in a future</div>
         )
      }
      
      export default Figure
      

      In that way, you will be able to pass to Figure something like img={props.someNode.frontmatter.imageNodeSource}.

      Again, without knowing your data structure is like a pie in the sky but get the idea.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search