(Gatsby) How to pass image source as a prop in MDX component - reactjs

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>
)
}

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.

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.)

Related

NextJS turn a .MDX into a component

I am trying to create an MDX blog and integrate it into my project. I currently have most of the setup done but I cannot for the life of me get MDX file into a component to render between my Layout. Here's my issue, all of the examples showcase something weird, for example,
MDX docs aren't typed, and I cannot get this to work in typescript
export default function Page({code}) {
const [mdxModule, setMdxModule] = useState()
const Content = mdxModule ? mdxModule.default : Fragment
useEffect(() => {
;(async () => {
setMdxModule(await run(code, runtime))
})()
}, [code])
return <Content />
}
And Nextjs just advises to put mdx files under /pages/, which doesn't fit my usecase
What I want:
Have an MDX file with Frontmatter YAML metadata
Load that MDX file into a component and display it
What I have currently
Nextjs config
import mdx from '#next/mdx';
import remarkFrontmatter from 'remark-frontmatter';
import remarkGfm from 'remark-gfm';
/** #type {import('next').NextConfig} */
const config = {
eslint: {
dirs: ['src'],
},
reactStrictMode: true,
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'tsx', 'ts'],
images: {
domains: [
'res.cloudinary.com',
'picsum.photos', //TODO
],
},
// SVGR
webpack: (config, options) => {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: [
{
loader: '#svgr/webpack',
options: {
typescript: true,
icon: true,
},
},
],
});
config.module.rules.push({
test: /\.mdx?$/,
use: [
options.defaultLoaders.babel,
{
loader: '#mdx-js/loader',
options: {
// providerImportSource: '#mdx-js/react',
remarkPlugins: [remarkFrontmatter, remarkGfm],
},
},
],
});
return config;
},
};
export default config;
My projects/[slug].tsx file
import { GetStaticPaths, GetStaticProps } from 'next';
import { getFileBySlugAndType, getFiles } from '#/lib/mdx/helpers';
import { Layout } from '#/components/layout/Layout';
import Seo from '#/components/Seo';
import * as runtime from 'react/jsx-runtime.js'
import { ProjectFrontMatter } from '#/types/frontmatter';
import { useEffect, useState } from 'react';
import {compile, run} from '#mdx-js/mdx'
import { MDXContent } from 'mdx/types';
type SingleProjectPageProps = {
frontmatter: ProjectFrontMatter;
content: string;
};
export default function SingleProjectPage({
frontmatter,
content,
}: SingleProjectPageProps) {
return (
<Layout>
<Seo
templateTitle={frontmatter.name}
description={frontmatter.description}
date={new Date(frontmatter.publishedAt).toISOString()}
/>
<main>
<section className=''>
<div className='layout'>
<div className='mt-8 flex flex-col items-start gap-4 md:flex-row-reverse md:justify-between'>
{/* <CustomLink
href={`https://github.com/theodorusclarence/theodorusclarence.com/blob/main/src/contents/projects/${frontmatter.slug}.mdx`}
>
Edit this on GitHub
</CustomLink>
<CustomLink href='/projects'>← Back to projects</CustomLink> */}
</div>
</div>
</section>
</main>
</Layout>
);
}
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await getFiles('projects');
return {
paths: posts.map((p) => ({
params: {
slug: p.replace(/\.mdx/, ''),
},
})),
fallback: false,
};
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const slug = params!['slug'] as string;
const { data, content } = getFileBySlugAndType(slug, 'projects');
return {
props: { frontmatter: data as ProjectFrontMatter, content },
};
};
And my helper function
export const getFileBySlugAndType = (slug: string, type: ContentType) => {
const file = readFileSync(
join(process.cwd(), 'src', 'content', type, slug + '.mdx'),
'utf-8'
);
const { data, content } = matter(file);
return { data, content };
};
I get as props in my SingleProjectPage, frontmatter data, and a string of the rest of the MDX content, which is correct, but I need to turn this string into MDX component. One of the libraries that does this is MDX-Bundler, but it hasn't been updated this year, and I'd prefer to use mdx-js if possible as it just release 2.0 version.

Show/Hide Section in dropdown menu for Gatsby Site

I am working on a Gatsby project.
I want to keep a folder of employees, each employee has it's own folder like src/staff/hanna-rosenfeld/... containing a index.mdx and a image file.
I want to source the staff's names and images to use in a component.
The goal is to build this:
my gatsby-config:
module.exports = {
siteMetadata: {
title: "Musikschule Weimar",
},
plugins: [
"gatsby-plugin-image",
"gatsby-plugin-sharp",
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages/`,
},
},
{
resolve: "gatsby-source-filesystem",
options: {
name: `staff`,
path: `${__dirname}/src/staff`,
}
},
"gatsby-plugin-mdx",
"gatsby-transformer-sharp",
"gatsby-transformer-remark",
`gatsby-remark-images`,
],
};
I already got the component that is doing the dropdown working:
import * as React from 'react'
import { useState, useEffect } from "react"
import { useStaticQuery, graphql } from 'gatsby'
import { BiChevronDown } from "react-icons/bi";
import StaffList from "./StaffList"
const rows = [
{
id: 1,
title: "Verantwortliche",
},
{
id: 2,
title: "Lehrende der Zupfinstrumente",
instrument: "zupfinstrumente"
},
{
id: 3,
title: "Lehrende der Blechblasinstrumente",
},
{
id: 4,
title: "Lehrende des Tasteninstruments",
},
{
id: 5,
title: "Lehrende des Gesangs",
},
{
id: 6,
title: "Lehrende des Schlagzeugs",
},
{
id: 7,
title: "Lehrende des Akkordeons",
},
{
id: 8,
title: "Lehrende der Musiktheorie",
},
{
id: 9,
title: "Lehrende der Früherziehung",
}
]
class DropDownRows extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<div className="dropdown-rows">
{rows.map(row => (
<div key={row.id}>
<div className="row">
<div className="col">{row.title}</div>
<div className="col">
<BiChevronDown
onClick={this.handleClick}
style={{float: "right"}}/>
</div>
<div>
</div>
</div>
{this.state.isToggleOn ? <StaffList /> : ''}
</div>
))}
</div>
)
}
}
export default DropDownRows
src/staff/hanna-rosenfeld/index.mdx
---
title: Hanna Rosenfeld
featuredImage: ./Foto_05.jpg
---
Hi, mein Name ist Hanna und ich bin ein full time web developerin.
my StaffList component:
import * as React from 'react'
import { StaticQuery, graphql } from 'gatsby'
import { GatsbyImage, getImage } from "gatsby-plugin-image"
function StaffList({ data }) {
return(
<StaticQuery
query={graphql`
query staffQuery {
allMdx {
edges {
node {
id
body
frontmatter {
title
featuredImage {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
}
}
`}
render={data => (
<div>
<h1>{data.allMdx.edges.map(edge => <h1 key={edge.node.id} data={edge.node}>{edge.node.frontmatter.title}</h1>)}</h1>
<GatsbyImage alt='some alt text' image={getImage(data.allMdx.edges.map(edge => edge.node.frontmatter.featuredImage))} />
</div>
)}
/>
)
}
export default StaffList
Querying for featuredImage works in graphiql, but i cannot get the image to show.
Console output:
"Warning: Failed prop type: The prop image is marked as required in
GatsbyImage, but its value is undefined."
The current state of the component on the site is this:
Getting the names to show up only in their category is another problem, for now I just want the images to show.
Thank you for any insight on a possible solution in advance.
There are a few things to comment on here:
The render structure is weird. You should do a loop through your data, as you are doing to display the h1 but at the same time, you can take advantage of the loop to display the image of each employee.
In addition, you are using a Gatsby Image v2 GraphQL query structure (gatsby-image) while the render component (GatsbyImage) is from v3 (gatsby-plugin-image). I'd recommend reading the migration guide.
To summarize, when you query for fluid or fixed images as you do while using the ...GatsbyImageSharpFluid fragment you are querying a v2 structure and the render component should be Img(from gatsby-image), that is accepting a fixed or a fluid image. However, when you use a GatsbyImage component, the passed prop should be a image (no matter if it's fixed or fluid). So, you have conflicting plugins and structures. You should get rid of one of them and use the corresponding structure. To recap:
fixed or fluid GraphQL query: v2 structure, the render component is Img
GatsbyImage component: v3 structure. I will base my answer on that assuming that you will migrate to v3 (v2 is deprecated).
The issue rises up because as I said,GatsbyImage is expecting an image prop while you are passing a loop at and a wrong query data at:
<GatsbyImage alt='some alt text' image={getImage(data.allMdx.edges.map(edge => edge.node.frontmatter.featuredImage))} />
Use something simpler as:
<div>
{data.allMdx.edges.map(edge => (
<article>
<h1 key={edge.node.id}>{edge.node.frontmatter.title}</h1>
<GatsbyImage alt='some alt text' image={getImage(edge.node.frontmatter.featuredImage)} />
</article>
))}
</div>
When you end the migration, the full snippet should look like:
import * as React from 'react'
import { StaticQuery, graphql } from 'gatsby'
import { GatsbyImage, getImage } from "gatsby-plugin-image"
function StaffList({ data }) {
return(
<StaticQuery
query={graphql`
query staffQuery {
allMdx {
edges {
node {
id
body
frontmatter {
title
featuredImage {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
}
}
}
}
`}
render={data => (
<div>
{data.allMdx.edges.map(edge => (
<article>
<h1 key={edge.node.id}>{edge.node.frontmatter.title}</h1>
<GatsbyImage alt='some alt text' image={getImage(edge.node.frontmatter.featuredImage)} />
</article>
))}
</div>
)}
/>
)
}
export default StaffList
Check the opening/closing brackets and to clean the cache while changing the data sources by running gatsby clean.

How to properly render images using a map function with Gatsby.js

This app was originally built using react, however we have decided to convert everything over and use Gatsbyjs. I am new to Gatsby and I am trying to get my images to render correctly using the artist data.
Here is how this part of the data was originally built:
const images = [
AngelAndJaamiHeadshot,
BillyHawkinsHeadshot,
BostonWellsHeadshot,
BreanahAlvizHeadshot,
CarloCollantesHeadshot,
CarloDarangHeadshot,
ChrisLumbaHeadshot,
ChrisMartinHeadshot,
CJDelaVegaHeadshot,
CyeBongalosHeadshot,
DanielKimHeadshot,
DavidDarkieSimmonsHeadshot,
// DavidDiosoHeadshot,
DavidSlaneyHeadshot,
DavinLawsonHeadshot,
DevinHeadshot,
DustinYuHeadshot,
EmilyRowanHeadshot,
HonrieDualanHeadhot,
HughAparenteHeadshot,
JaamiWaaliVillalobosHeadshot,
JeremyBorjaHeadshot,
JonathanSisonHeadshot,
JordanBautistaHeadshot,
JordanRileyHeadshot,
JuliaKestnerHeadshot,
JustinArcegaHeadshot,
KaitlynSungHeadshot,
KaylarPrieteHeadshot,
KeyanaReedHeadshot,
KikoJamesHeadshot,
KirstieAndJeremyHeadshot,
KirstieHeadshot,
KJEstudilloHeadshot,
LarkinPoyntonHeadshot,
MitchVillarealHeadhsot,
MoanaRakanaceHeadshot,
NoelleFrancoHeadshot,
PhuongLeHeadshot,
SamMooreHeadshot,
TonyRayHeadshot,
TracySeilerHeadshot,
TrishaOcampoHeadshot,
YutaNakamuraHeadshot,
defaultHeadshot,
]
export const buildArtistsData = (artists) => {
return artists.map((artist, idx) => {
return {
...artist,
imageUrl:
idx >= 43
? defaultHeadshot
: images[`${artist.firstName}${artist.lastName}Headshot`],
}
})
}
And this is how it was used in my Artists component:
const ArtistsPage = () => {
const artists = buildArtistsData(ARTISTS)
...
<div className={classes.flexContainer}>
{artists
.map(
(
{ city, currentTeam, firstName, lastName, imageUrl },
idx: number
) => {
return (
<div className={classes.flexItem} key={idx}>
<img
className={classes.artistCardImg}
src={imageUrl}
alt='artist-image'
/>
<div className={classes.artistCardName}>
{`${firstName} ${lastName}`.toUpperCase()}
</div>
<div className={classes.artistCardText}>{city}</div>
<div className={classes.artistCardText}>{currentTeam}</div>
</div>
)
}
)}
</div>
But now that I am using Gatsbyjs and data none of the above will work anymore. Here is what I am working with on the converted Gatsbyjs page:
import React from 'react'
import PropTypes from 'prop-types'
import { StaticImage } from 'gatsby-plugin-image'
import Img from 'gatsby-image'
import { graphql } from 'gatsby'
import { useStyles } from './styles'
const ArtistsPage = ({ data }) => {
console.log(data)
const classes = useStyles()
// const { images } = props
return (
<section>
<article className={classes.artistsContainer}>
<div className={classes.flexContainer}>
{data.allArtistsJson.edges.map(({ node }, idx) => {
return (
<div className={classes.flexItem} key={idx}>
<div>
{images.map((img, idx) => (
<Img
key={idx}
fluid={img.node.childImageSharp.fluid}
/>
))}
</div>
<div className={classes.artistCardName}>
{`${node.firstName} ${node.lastName}`.toUpperCase()}
</div>
<div className={classes.artistCardText}>{node.city}</div>
<div className={classes.artistCardText}>{node.currentTeam}</div>
</div>
)
})}
</div>
</article>
</section>
)
}
export const pageQuery = graphql`
query {
headshots: allFile(filter: { absolutePath: { regex: "/headshots/" } }) {
edges {
node {
childImageSharp {
fluid(maxWidth: 600) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`
ArtistsPage.propTypes = {
firstName: PropTypes.string,
lastName: PropTypes.string,
currentTeam: PropTypes.string,
headshots: PropTypes.string,
dropdown: PropTypes.string,
data: PropTypes.array,
images: PropTypes.string,
}
export default ArtistsPage
I was trying to pull image data as props using
const { image } = props - but that throws an error so I am really confused as to what and how to map over this to pull my images in for the correct artist.
Also here is my config.js file for reference:
const path = require('path')
module.exports = {
siteMetadata: {
title: 'Platform Showcase',
},
plugins: [
'gatsby-plugin-gatsby-cloud',
'gatsby-plugin-image',
// {
// resolve: "gatsby-plugin-google-analytics",
// options: {
// trackingId: "",
// },
// },
'gatsby-plugin-react-helmet',
'gatsby-plugin-sitemap',
{
resolve: 'gatsby-plugin-manifest',
options: {
icon: 'src/images/icon.png',
},
},
'gatsby-plugin-mdx',
'gatsby-plugin-sharp',
'gatsby-transformer-sharp',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'images',
path: './src/images/',
},
__key: 'images',
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `headshots`,
path: `${__dirname}/src/images/artists/headshots`,
},
__key: 'headshots',
},
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'pages',
path: './src/pages/',
},
__key: 'pages',
},
{
resolve: 'gatsby-plugin-google-fonts',
options: {
fonts: ['material icons', 'roboto:300,400,500,700'],
},
},
`gatsby-theme-material-ui`,
`gatsby-transformer-json`,
{
resolve: `gatsby-source-filesystem`,
options: {
path: `./src/data/`,
},
},
{
resolve: 'gatsby-plugin-root-import',
options: {
src: path.join(__dirname, 'src'),
containers: path.join(__dirname, 'src/containers'),
images: path.join(__dirname, 'src/images'),
},
},
],
}
Any help anyone is willing to give would be greatly appreciated.
Your data, when using page queries, is always under props.data so your nesting should look like:
{data.headshots.edges.map(({ node }, idx) => {
return (
<div className={classes.flexItem} key={idx}>
<Img fluid={node.childImageSharp.fluid} />
</div>
)
})}
Note: I'm assuming that your GraphQL query retrieves properly the data. Test it at localhost:8000/___graphql and tweak your filesystem if needed
Each image is each node itself (according to query structure) so, since your query is aliasing your allFile as headshots, your data is stored in props.data.headshots.

How to map multiple yaml files to frontmatter in Gatsby

The problem:
I am having trouble mapping a second .yaml file to a second markdown frontmatter field in Gatsby. The project is for a magazine, with the first yaml file storing details about each author and the second file storing details about each issue.
The author.yaml file worked like it was supposed to, and I can query it across every template and page, but the issue.yaml file only works in two places: templates where I specifically passed its data as page context in gatsby-node, and (inexplicably) for markdown files with the frontmatter "featured" field === true. For mapped arrays in which "featured" is not present, or when it's false, every query that should pull data from the issue.yaml gives "TypeError: Cannot read property 'id' of null."
My suspicion is that it's because the issue field on my markdown files isn't the first field (which is author, and already mapped to the author.yaml file). As far as I can tell, I've implemented both yaml files exactly the same.
Things I've tried:
I've tried to query the allAuthorYaml and allIssueYaml nodes that I think are supposed to be automatically generated, but I couldn't get either of them to work. I also tried to create a schema customization in gatsby-node, but I couldn't figure out how to adapt the tutorial to define the nodes I needed globally (way over my head). I read somewhere that I could import the yaml data directly with import YAMLData from "../data/issue.yaml" and then create an array directly, but it also gave me null/undefined errors.
I am a hobbyist, and don't have a background in programming at all, which is probably a big part of the problem. Still, I'd be grateful for any help anyone might have. And if anyone happens to spot any other places my code needs improving, definitely let me know!
The starter I used was https://www.gatsbyjs.org/starters/JugglerX/gatsby-serif-theme/.
My repository is at https://github.com/ljpernic/HQ3.1/tree/HQhelp.
Thanks again!
My gatsby-config:
module.exports = {
siteMetadata: {
title: 'Haven Quarterly',
description: 'a magazine of science fiction and fantasy',
submit: {
phone: 'XXX XXX XXX',
email: 'havenquarterly#gmail.com',
},
menuLinks: [
{
name: 'Fiction',
link: '/fiction',
},
{
name: 'Non-fiction',
link: '/non-fiction',
},
{
name: 'Letters from the Future',
link: '/future',
},
{
name: 'Full Issues',
link: '/fullissues',
},
{
name: 'Contributors',
link: '/contributors',
},
{
name: 'About',
link: '/about',
},
{
name: 'Support',
link: '/support',
},
{
name: 'Submit',
link: '/submit',
},
],
},
plugins: [
'gatsby-plugin-sass',
'gatsby-transformer-json',
'gatsby-transformer-remark',
'gatsby-plugin-react-helmet',
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
`gatsby-plugin-catch-links`,
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/pages`,
name: 'pages',
},
},
/* {
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/posts`,
name: 'posts',
},
},*/
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/data`,
name: 'data',
},
},
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/images`,
name: 'images',
},
},
{
resolve: "gatsby-transformer-remark",
options: {
plugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 1200,
quality: 95,
},
},
],
},
},
{
resolve: 'gatsby-plugin-google-analytics',
options: {
trackingId: guid ? guid : 'UA-XXX-1',
// Puts tracking script in the head instead of the body
head: false,
},
},
`gatsby-transformer-yaml`,
],
mapping: {
// 3. map author to author.yaml
"MarkdownRemark.frontmatter.author": `AuthorYaml`,
"MarkdownRemark.frontmatter.issue": `IssueYaml`,
},
};
My gatsby-node:
const _ = require('lodash');
const fs = require("fs")
const yaml = require("js-yaml")
// Create pages from markdown files
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions;
const ymlDoc = yaml.safeLoad(fs.readFileSync("./src/data/author.yaml", "utf-8"))
const ymlIssueDoc = yaml.safeLoad(fs.readFileSync("./src/data/issue.yaml", "utf-8"))
return new Promise((resolve, reject) => {
resolve(
graphql(
`
query {
fictionarchive: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/fiction/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
frontmatter {
category
featured
path
title
date(formatString: "DD MMMM YYYY")
}
excerpt
}
}
}
nonfictionarchive: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/non-fiction/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
frontmatter {
category
featured
path
title
date(formatString: "DD MMMM YYYY")
}
excerpt
}
}
}
futurearchive: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/letters/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
frontmatter {
category
featured
path
title
date(formatString: "DD MMMM YYYY")
}
excerpt
}
}
}
issuesarchive: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
frontmatter {
category
featured
path
title
date(formatString: "DD MMMM YYYY")
}
excerpt
}
}
}
authorarchive: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
frontmatter {
category
featured
path
title
date(formatString: "DD MMMM YYYY")
}
excerpt
}
}
}
}
`,
).then((result) => {
ymlDoc.forEach(element => {
createPage({
path: element.idpath,
component: require.resolve("./src/templates/eachauthor.js"), /*creates INDIVIDUAL AUTHOR PAGES*/
context: {
idname: element.id,
bio: element.bio,
twitter: element.twitter,
picture: element.picture,
stories: element.stories,
},
});
});
ymlIssueDoc.forEach(element => {
createPage({
path: element.idpath,
component: require.resolve("./src/templates/eachissue.js"), /*creates INDIVIDUAL ISSUE PAGES*/
context: {
issueidname: element.id,
text: element.text,
currentcover: element.currentcover,
artist: element.artist,
artistbio: element.artistbio,
artistimage: element.artistimage,
},
});
});
result.data.fictionarchive.edges.forEach(({ node }) => {
const component = path.resolve('src/templates/eachpost.js'); /*creates INDIVIDUAL FICTION PAGES*/
createPage({
path: node.frontmatter.path,
component,
context: {
id: node.id,
},
});
});
result.data.nonfictionarchive.edges.forEach(({ node }) => {
const component = path.resolve('src/templates/eachpost.js'); /*creates INDIVIDUAL NON-FICTION PAGES*/
createPage({
path: node.frontmatter.path,
component,
context: {
id: node.id,
},
});
});
result.data.futurearchive.edges.forEach(({ node }) => {
const component = path.resolve('src/templates/eachpost.js'); /*creates INDIVIDUAL LETTER PAGES*/
createPage({
path: node.frontmatter.path,
component,
context: {
id: node.id,
},
});
});
result.data.issuesarchive.edges.forEach(({ node }) => {
const component = path.resolve('src/templates/eachpost.js'); /*creates INDIVIDUAL ISSUE PAGES; change template to change every issue page*/
createPage({
path: node.frontmatter.path,
component,
context: {
id: node.id,
},
});
});
const FICposts = result.data.fictionarchive.edges /*creates FICTION LIST PAGES*/
const FICpostsPerPage = 10
const FICnumPages = Math.ceil(FICposts.length / FICpostsPerPage)
Array.from({ length: FICnumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/fiction` : `/fiction/${i + 1}`,
component: path.resolve('src/templates/fictionarchive.js'),
context: {
limit: FICpostsPerPage,
skip: i * FICpostsPerPage,
FICnumPages,
FICcurrentPage: i + 1,
},
});
});
const NONFICposts = result.data.nonfictionarchive.edges /*creates NON-FICTION LIST PAGES*/
const NONFICpostsPerPage = 10
const NONFICnumPages = Math.ceil(NONFICposts.length / NONFICpostsPerPage)
Array.from({ length: NONFICnumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/non-fiction` : `/non-fiction/${i + 1}`,
component: path.resolve('src/templates/nonfictionarchive.js'),
context: {
limit: NONFICpostsPerPage,
skip: i * NONFICpostsPerPage,
NONFICnumPages,
NONFICcurrentPage: i + 1,
},
});
});
const FUTposts = result.data.futurearchive.edges /*creates LETTERS FROM THE FUTURE LIST PAGES*/
const FUTpostsPerPage = 10
const FUTnumPages = Math.ceil(FUTposts.length / FUTpostsPerPage)
Array.from({ length: FUTnumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/future` : `/future/${i + 1}`,
component: path.resolve('src/templates/futurearchive.js'),
context: {
limit: FUTpostsPerPage,
skip: i * FUTpostsPerPage,
FUTnumPages,
FUTcurrentPage: i + 1,
},
});
});
const FULLposts = result.data.issuesarchive.edges /*creates ISSUES LIST PAGES*/
const FULLpostsPerPage = 10
const FULLnumPages = Math.ceil(FULLposts.length / FULLpostsPerPage)
Array.from({ length: FULLnumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/fullissues` : `/fullissues/${i + 1}`,
component: path.resolve('src/templates/issuesarchive.js'),
context: {
limit: FULLpostsPerPage,
skip: i * FULLpostsPerPage,
FULLnumPages,
FULLcurrentPage: i + 1,
},
});
});
const AUTposts = result.data.authorarchive.edges
const AUTpostsPerPage = 10
const AUTnumPages = Math.ceil(AUTposts.length / AUTpostsPerPage)
Array.from({ length: AUTnumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/contributors` : `/contributors/${i + 1}`,
component: path.resolve('src/templates/authorarchive.js'),
context: {
limit: AUTpostsPerPage,
skip: i * AUTpostsPerPage,
AUTnumPages,
AUTcurrentPage: i + 1,
},
});
});
resolve();
}),
);
});
};
My front page (shortened):
import { graphql, withPrefix, Link } from 'gatsby';
import Image from "gatsby-image";
import Helmet from 'react-helmet';
import SEO from '../components/SEO';
import Layout from '../layouts/index';
const Home = (props) => { //THIS SETS THE FRONT PAGE, including featured story, latest stories, and latest issues
const json = props.data.allFeaturesJson.edges;
const posts = props.data.allMarkdownRemark.edges;
return (
<Layout bodyClass="page-home">
<SEO title="Home" />
<Helmet>
<meta
name="Haven Quarterly"
content="A Magazine of Science Fiction and Fantasy"
/>
</Helmet>
{/*FEATURED*/}
<div className="intro pb-1">
<div className="container">
<div className="row2 justify-content-start">
<div className="grid-container pt-2">
<div className="wide">
<div className="col-12">
<Link to="/featured">
<h4>Featured Story</h4>
</Link>
<hr />
</div>
{posts
.filter(post => post.node.frontmatter.featured === true) /*This looks at only the md file with featured: true*/
.map(({ node: post }) => {
return (
<div className="container" key={post.id}>
<h1 pb>
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link>
</h1>
<h2>By <Link to={post.frontmatter.author.idpath}> {post.frontmatter.author.id}</Link> in <Link to={post.frontmatter.issue.idpath}> {post.frontmatter.issue.id}</Link></h2> /*THIS IS THE ONLY PLACE ON THIS PAGE WHERE THE ISSUE YAML ARRAY SEEMS TO WORK. IT ONLY WORKS WHEN featured === true WHICH IS CRAZY*/
<p>{post.excerpt}</p>
</div>
)
})}
</div>
<div className="thin">
{posts
.filter(post => post.node.frontmatter.featured === true) /*This looks at only the md file with featured: true*/
.map(({ node: post }) => {
return (
<Link to="/latest">
<Image className="topimage"
fixed={post.frontmatter.currentcover.childImageSharp.fixed} /*This pulls the image from the md file with featured: true (current cover)*/
/>
</Link>
)
})}
</div>
</div>
<hr />
<div className="col-12">
{posts
.filter(post => !post.node.frontmatter.featured)
.filter(post => post.node.frontmatter.issue === "Issue One Summer 2020") /*THIS SHOULD FILTER ONLY MD FILES WITH issue: Issue One Summer 2020"*/
.slice(0, 6)
.map(({ node: post }) => {
return (
<div className="postbody" key={post.id}>
<h2 pb>
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link> by <Link to={post.frontmatter.author.idpath}> {post.frontmatter.author.id}</Link> ({post.frontmatter.category})
</h2>
</div>
)
})}
</div>
<hr />
</div>
</div>
</div>
<div className="postbody">
<div className="container pt-8 pt-md-4">
<div className="row2 justify-content-start pt-2">
<div className="col-12">
<Link to="/fiction">
<h4>Latest Fiction</h4>
</Link>
<hr />
</div>
{/*FICTION*/}
<div className="container">
{posts
.filter(post => !post.node.frontmatter.featured)
.filter(post => post.node.frontmatter.category === "fiction") /*This should only pull from md files with category "fiction", excluding posts marked featured*/
.slice(0, 6)
.map(({ node: post }) => {
return (
<div className="container" key={post.id}>
<Image className="inlineimage"
fluid={post.frontmatter.cover.childImageSharp.fluid} /*This should pull image from md files with category "fiction"*/
/>
<h1 pb>
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link>
</h1>
<h2>By <Link to={post.frontmatter.author.idpath}> {post.frontmatter.author.id}</Link> in <Link to={post.frontmatter.issue}> {post.frontmatter.issue}</Link></h2>
<p>{post.excerpt}</p>
<hr />
</div>
)
})}
<div className="col-12 text-center pb-3">
<Link className="button button-primary" to="/fiction">
View All Stories
</Link>
</div>
</div>
</div>
</div>
</div>
</Layout>
);
};
export const query = graphql`
query {
allAuthorYaml {
nodes {
bio
id
idpath
picture {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
stories {
item
}
twitter
}
}
allIssueYaml {
edges {
node {
artist
artistbio
id
idpath
text
artistimage {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
currentcover {
childImageSharp {
fixed(width: 403) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 300, maxHeight: 300) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/.*.md$/" }}
sort: { fields: [frontmatter___date], order: DESC }
) {
totalCount
edges {
node {
id
frontmatter {
featured
path
title
author {
id
idpath
bio
twitter
picture {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
}
issue {
id
idpath
currentcover {
childImageSharp {
fixed(width: 403) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
text
artist
artistimage {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
artistbio
}
date(formatString: "DD MMMM YYYY")
category
currentcover {
childImageSharp {
fixed(width: 403) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
cover {
childImageSharp {
fixed(width: 403) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
}
excerpt(pruneLength: 650)
}
}
}
allFeaturesJson {
edges {
node {
id
title
description
image
}
}
}
}
`;
export default Home;
A typical template with broken array:
import { graphql, Link, withPrefix } from 'gatsby';
import SEO from '../components/SEO';
import Layout from '../layouts/index';
import Helmet from 'react-helmet';
import Image from 'gatsby-image';
export default class Fictionarchive extends React.Component {
render() {
const posts = this.props.data.allMarkdownRemark.edges
const json = this.props.data.allFeaturesJson.edges;
const { FICcurrentPage, FICnumPages } = this.props.pageContext
const isFirst = FICcurrentPage === 1
const isLast = FICcurrentPage === FICnumPages
const prevPage = FICcurrentPage - 1 === 1 ? "/" : `/fiction/${FICcurrentPage - 1}`
const nextPage = `/fiction/${FICcurrentPage + 1}`
return (
<Layout bodyClass="page-home">
<SEO title="Fiction" />
<Helmet>
<meta
name="description"
content="all fiction of Haven Quarterly"
/>
</Helmet>
<div className="postbody">
<div className="container pt-md-5">
<div className="row2 justify-content-start">
<div className="col-12">
<h3>Latest Fiction</h3>
<hr />
</div>
<div className="container">
{posts
.filter(post => post.node.frontmatter.category === "fiction")
.map(({ node: post }) => {
return (
<div className="container" key={post.id}>
<Image className="inlineimage"
fluid={post.frontmatter.cover.childImageSharp.fluid}
/>
<h1 pb>
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link>
</h1>
<h2>By <Link to={post.frontmatter.author.idpath}> {post.frontmatter.author.id}</Link> in <Link to={post.frontmatter.issue.idpath}> {post.frontmatter.issue.id}</Link></h2>
<p>{post.excerpt}</p>
<hr />
</div>
)
})}
<div className="container">
<div className="row">
<div className="col-sm">
<p className="text-left">
{!isFirst && (
<Link to={prevPage} rel="prev">
← Previous Page
</Link>
)}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Layout>
)
}
}
export const fictionarchiveQuery = graphql`
query fictionarchiveQuery($skip: Int!, $limit: Int!) {
allMarkdownRemark(
filter: { frontmatter: {category:{eq:"fiction"} } }
sort: { fields: [frontmatter___date], order: DESC }
limit: $limit
skip: $skip
) {
edges {
node {
excerpt(pruneLength: 750)
frontmatter {
category
featured
path
title
author {
id
idpath
bio
twitter
picture {
childImageSharp {
fixed(width: 400) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 400, maxHeight: 400) {
...GatsbyImageSharpFluid
}
}
}
}
issue {
id
idpath
currentcover {
childImageSharp {
fixed(width: 403) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
text
artist
artistimage {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
artistbio
}
date(formatString: "DD MMMM YYYY")
cover {
childImageSharp {
fixed(width: 322) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 450) {
...GatsbyImageSharpFluid
}
}
}
}
html
}
}
}
allFeaturesJson {
edges {
node {
id
title
description
image
}
}
}
}
`
Solution
Be smarter than me and make sure you've actually defined all of the values you have in your markdown files in your yaml file.
Longer Explanation
I asked on the GitHub repository for Gatsby, and the solution turned out to be really easy:
Everything was set up more or less correctly. The problem turned out to be that in my second yaml file, I wasn't defining all of the values that I was listing in my markdown files. So, between the 30+ .md files, I had four values total for the issue field, but in my issue.yaml file, I was only defining two of them. It returned a null error because when it cycled through the markdown files, it said, "I have this third value, but nothing that corresponds to it in the yaml file."
The person who answered my question said there were two solutions:
Define all issue IDs in issue.yaml so that you get at least the id
back
Define an alternative issueTitle in your frontmatter of the posts
and check whether issue returns null. If yes, hide your components and
use issueTitle instead
I thought this didn't matter because I thought I was only calling data from markdown files with the fields I had defined (in other words, I thought I was already excluding those .md files that I hadn't defined in my yaml), but that was erroneous.
Other Things to Note
Two other simple issues that tripped me up: 1. Make sure that the spelling is exactly the same between what's defined in the yaml file and what is provided in the corresponding field of the markdown file. 2. Make sure you use an id field, as per this suggestion, when mapping your yaml file to a frontmatter field.
Final Thoughts
Turns out I don't know what I'm doing. But I hope that this answer helps anyone else noodling around with Gatsby who gets stuck on the same problem.
Link to the fuller answer kindly provided by LekoArts: https://github.com/gatsbyjs/gatsby/issues/25373

Images in markdown not loading in template in gatsbyjs

Just like this tutorial Working with Images in Markdown Posts and Pages; I am trying to load a blog post's image from my markdown(frontmatter) to my template header
featuredImage: "../images/hiplife.jpg"
My template component looks like this:
import React from "react"
import { graphql } from "gatsby"
import { Img } from "gatsby-image"
import Layout from "../../components/layout/layout"
import Navbar from "../../components/navbar/navbar"
import styles from "./blogposts.module.css"
import kasahare from "../../images/pwd.png"
export default ({ data }) => {
let post = data.markdownRemark
let featuredImgFluid = post.frontmatter.featuredImage.childImageSharp.fluid
return (
<Layout>
<Navbar />
<div className={styles.Header}>
<Img fluid={featuredImgFluid} />
</div>
<div className={styles.BlogPosts}>
<div className={styles.BlogPostsHolder}>
<div className={styles.authorside}>
<div className={styles.author}>
<div>
<img className={styles.authorImg} src={kasahare} alt="Logo" />
</div>
<div>{post.author}</div>
<div>{post.date}</div>
<div className={styles.tag}>{post.tag}</div>
</div>
</div>
<div
className={styles.blogpostcontent}
dangerouslySetInnerHTML={{ __html: data.markdownRemark.html }}
/>
</div>
</div>
</Layout>
)
}
export const pageQuery = graphql`
query($path: String!) {
markdownRemark(frontmatter: { path: { eq: $path } }) {
html
frontmatter {
date(formatString: "MMMM DD, YYYY")
author
title
tag
featuredImage {
childImageSharp {
fluid(maxWidth: 1050) {
base64
aspectRatio
src
srcSet
sizes
}
}
}
}
}
}
`
But I keep getting this error.
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
My gatsby-config looks like
module.exports = {
/* Your site config here */
siteMetadata: {
title: "HipLife",
description: "The blog for hiplife culture",
author: "kaf",
},
plugins: [
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
`gatsby-transformer-remark`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `markdown`,
path: `${__dirname}/src/markdown`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images/`,
},
},
],
}
And my gatsby-node
const path = require(`path`)
exports.createPages = async ({ actions, graphql, reporter }) => {
const { createPage } = actions
const blogPostTemplate = path.resolve(`src/templates/blogposts/blogposts.js`)
const result = await graphql(`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 1000
) {
edges {
node {
frontmatter {
path
}
}
}
}
}
`)
// Handle errors
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.frontmatter.path,
component: blogPostTemplate,
context: {}, // additional data can be passed via context
})
})
}
Found out I had curly braces around my gatsby-image import import { Img } from "gatsby-image"
Removing them got it working. Thanks

Resources