Dynamically rendered images in Gatsby - javascript

I'm working on the blog based on React, TS and Gatsby.
Blog posts are based on markdown. Each blog post will have a similar header with title, time necessary to read the article and the logos of the technologies that the particular blog post will be about.
I have a problem with rendering those images dynamically. My idea was to create something like this in markdown
---
path: "/fourth"
date: "2021-06-02"
title: "TypeScript - intro"
readTime: "140"
author: "Adam Kniec"
imgs: [typescript, react]
---
That's the fourth blog post
after that I wanted to create a graphql query and get the imgs names like so:
export const postQuery = graphql`
query BlogPostByPath($path: String!) {
markdownRemark(frontmatter: { path: { eq: $path } }) {
html
frontmatter {
path
readTime
title
author
imgs
date
}
}
}
`;
I have the array of images in the props now and I wanted to render those images like this
{data.markdownRemark.frontmatter.imgs.map((imgPath) => {
const imgPatha = `../images/${imgPath}`;
return <StaticImage src={imgPatha} alt="asdasd" />;
})}
Unfortunately, gatsby gives me the warning
react_devtools_backend.js:2560 Image not loaded ../images/typescript
Is this the correct approach ? Please let me know what I'm doing wrong or how to render those images dynamically.

As it has been said by #coreyward you can't use dynamic props in the StaticImage component, it's a known restriction.
That said, you have two choices:
Using the standard img HTML tag.
Using the GatsbyImage component. To do it, you'll need to add the images in your filesystem to allow Gatsby to create the proper nodes and then, you will need to query them in your pages/templates. Without further details on the implementation, it's impossible to guess how your code should look like but the idea relies on something like:
import { graphql } from "gatsby"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
function BlogPost({ data }) {
const image = getImage(data.blogPost.avatar)
return (
<section>
<h2>{data.blogPost.title}</h2>
<GatsbyImage image={image} alt={data.blogPost.author} />
<p>{data.blogPost.body}</p>
</section>
)
}
export const pageQuery = graphql`
query {
blogPost(id: { eq: $Id }) {
title
body
author
avatar {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
}
`

Related

Fetch only one element from Graphql in Gatsby

am new to gatsby and graphql and I came across a tutorial where it is mentioned to fetch all the data using .map. But I want to fetch only one element from the DB. So how do I do it?
import React from "react";
import Layout from "../components/layout";
import { useStaticQuery, graphql, Link } from "gatsby";
const Blogs = () => {
const data = useStaticQuery(
graphql`
query {
allMarkdownRemark(sort: { frontmatter: { date: ASC } }) {
edges {
node {
frontmatter {
title
date(formatString: "DD MM, YYYY")
}
excerpt
id
fields {
slug
}
}
}
}
}
`
);
return (
<Layout>
<ul>
{data.allMarkdownRemark.edges.map((edge) => {
return (
<li key={edge.node.id}>
<h2>
<Link to={`/blog/${edge.node.fields.slug}/`}>
{edge.node.frontmatter.title}
</Link>
</h2>
<div>
<span>
Posted on {edge.node.frontmatter.date}
</span>
</div>
<p>{edge.node.excerpt}</p>
<div>
<Link to={`/blog/${edge.node.fields.slug}/`}>Read More</Link>
</div>
</li>
);
})}
</ul>
</Layout>
);
};
export default Blogs;
Lets say I have multiple blogs and I wish to show only a specific one in a page through query like...
query MyQuery {
markdownRemark((id: {eq: "9ac19d6d"}) //Some ID {
title
description
content
}
}
How to get this on a page to display?
Thanks in advance!
Depending on what do you want to achieve:
If you want just a specific single post. You can filter your useStaticQuery to add the value of the id (if you know it beforehand) like:
query MyQuery {
markdownRemark((id: {eq: "123"}) {
title
description
content
}
}
useStaticQuery as the name points out, is static and doesn't accept dynamic values.
Another alternative is to get a specific position from data.allMarkdownRemark to display it.
If you just want a single post without any filter you can take advantage of the GraphQL query options:
{
allMarkdownRemark(limit: 1) {
edges {
node {
frontmatter {
title
}
}
}
}
}
If you are trying to create dynamic posts, hence each post template will display a different blog post (one per template), you need to pass a filter value from gatsby-node.js (where you create the post pages) to the template through Gatsby's context:
// gatsby-node.js
posts.forEach(({ node }, index) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
id: node.id,
title: node.title
},
})
})
Note: here I'm lifting the id and the title. Use whatever works better for you
Now, you can take advantage of the context in your Blogs component (as long as it's a template):
const Blogs = ({data}) => {
console.log("your blog post is:", data)
return (
<Layout>
<h1>{data.markdownRemark.title}</h1>
</Layout>
);
};
export const query = graphql`
query($id: String!, $title: String!) {
markdownRemark((id: {eq: $id}) {
title
description
content
}
}
`
export default Blogs;
In other words: the first approach uses a static query (via useStaticQuery hook. Static, no dynamic parameters allowed) and the second uses a page query (only available in pages or templates)
With your query:
query MyQuery {
markdownRemark((id: {eq: "9ac19d6d"}) //Some ID {
title
description
content
}
}
Your data will be in data.markdownRemark
You can access those 3 fields directly.
const { title, description, content ] = data.markdownRemark;
return (
<Layout>
<div>
<p>{title}</p>
<p>{description]</p>
<p>{content}</p>
</div>
</Layout>
)

GraphQL Query to Array List using React?

I am currently working on a gatsby website and am mostly using react components doing so. The way our jobs posting page currently works is that it fetches a list of jobs from an array that stores the information for each position and maps them out with proper stylization.
positions.js (section)
const Positions = () => (
<StyledPositions>
<GlobalContainer>
{PositionList.map((position, index) => (
<StyledPosition key={index}>
<StyledPositionName>
<Link to={position.link} activeClassName="active">
{position.name}
</Link>
</StyledPositionName>
</StyledPosition>
))}
</GlobalContainer>
</StyledPositions>
);
export default Positions;
PositionList is the array and it looks like this.
positionslist.js
const Positionlist = [
{
name: "Senior Software Engineer- Infrastructure",
link: "/careers/open_positions/sr_cloud_eng",
},
{
name: "System Software Engineer",
link: "/careers/open_positions/system_soft_eng",
},
{
name: "Software Engineer (Database Development)",
link: "/careers/open_positions/soft_eng_database",
},
];
export default Positionlist;
What I'm trying to do is to populate the job posting site from a GraphQL query from an external job posting management site. I am able to fetch the information fine, but I'd like to somehow turn the information into an array like positionslist.js so that positions.js can simply map the information the same way. The query looks like this
query MyQuery {
allLever {
edges {
node {
text
categories {
commitment
location
team
}
applyUrl
}
}
}
}
All GraphQL queries store their result inside props.data as you can see in Gatsby's tutorial. At this point, you only need to enter to the nested structure like:
import * as React from 'react'
import { graphql } from 'gatsby'
const Positions = ({data}) => {
let PositionList = data.edges;
return <StyledPositions>
<GlobalContainer>
{PositionList.map(({node: position}) => (
<StyledPosition key={position.name}>
<StyledPositionName>
<Link to={position.link} activeClassName="active">
{position.name}
</Link>
</StyledPositionName>
</StyledPosition>
))}
</GlobalContainer>
</StyledPositions>
}
export const query = graphql`
query MyQuery {
allLever {
edges {
node {
name: text
categories {
commitment
location
team
}
link: applyUrl
}
}
}
}
`
export default Positions
Note: it's better to avoid using index as key so you can use another field
Keep in mind that you barely need to change your previous structure (because of some destructuring + aliasing) but, what's important is that page queries, only work in pages (hence the name) so I'm assuming that Positions is a separate page. Otherwise, you will need to pass down the props from the page component.
Because of the aliasing, applyUrl will be aliased as link and text to name, so your previous structure will work exactly in the same way.

ChildImageSharp is null after creating custom schema in gatsby-node

I'm building a website with gatsby and Strapi and i'm facing an issue that can't fix.
Here's my problem:
I made a custom graphQL Schema to avoid building fails if my clients leave empty fields in the CMS. But since i did that for my homepage banner, childImageSharp is null.
I'm assuming i did my custom graphql schemas wrong but I can't understand where.
Here's my files:
Homepage.js
const IndexPage = ({ data }) => {
return (
<Layout>
{data.allStrapiBanner.edges[0].node.banner.childImageSharp.fixed &&
<Img
fixed={data.allStrapiBanner.edges[0].node.banner.childImageSharp.fixed}
imgStyle={{ position: "static" }}
/>
}
</Layout>
)
}
export default IndexPage
export const pageQuery = graphql`
allStrapiBanner {
edges {
node {
banner {
childImageSharp {
fixed(width: 800){
src
}
}
}
}
}
}
}
`
Here's my gatsby-node.js custom Schema:
exports.sourceNodes = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type allStrapiBanner implements Node {
banner: childImageSharp
}
type childImageSharp{
childImageSharp: fixed
}
type fixed{
fixed(width: Int): src
}
type src{
src: String
}
`
createTypes(typeDefs)
}
Now gatsby build without error when i don't upload an image to my banner field but when i upload an image childImageSharp is null
I already read the doc (https://www.gatsbyjs.com/docs/reference/graphql-data-layer/schema-customization/) but i can't manage to make it work.
if someone can give me a tips to how to correctly create my custom schemas for childImageSharp.
Thanks you very much in advance
SOLVED
After a lot of hours of brute force trying to understand what was wrong, I have finally fixed my problem. I can't believe it was so simple:
I simply changed my schema in gastby-node.js to
exports.sourceNodes = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type allStrapiBanner implements Node {
banner: File
}
`
createTypes(typeDefs)
}
and it work

GatsbyJS - TypeError: Cannot read property 'childImageFluid' of undefined

I am working on a Gatsby website, and I keep getting "TypeError: Cannot read property 'childImageFluid' of undefined"
The code I have is this in my Project.js file
import React from "react"
import PropTypes from "prop-types"
import Image from "gatsby-image"
import { FaGithubSquare, FaShareSquare } from "react-icons/fa"
const Project = ({description, title, github, stack, url, image, index}) => {
return (
<article className="project">
<Image fluid={image.childImageFluid.fluid} className="project-img" />
</article>
)
}
Project.propTypes = {}
export default Project
and I have the graphql set up in the index.js file where it will be displayed, and everything is working as it should in graphql...
export const query = graphql`
{
allStrapiProjects(filter: { featured: { eq: true } }) {
nodes {
github
id
description
title
url
image {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
stack {
id
title
}
}
}
}
`
everything up to the what I am working on in the Project.js file is in my github - https://github.com/matthewbert86/gatsby-site but all of that code is in the first code section above.
When you use a page query in GraphQL, your gathered data is stored inside a data object (as a props). You need to iterate through it until you get your fluid image. It should be in: props.data.allStrapiProjects.nodes.image.childImageFluid.fluid. Since you are destructuring everything in your <Project> component:
const Project = ({ data }) => {
let { description, title, github, stack, url, image } = data.allStrapiProjects.nodes; // your data is here
return (
<article className="project">
<Img fluid={data.allStrapiProjects.nodes.image.childImageFluid.fluid} className="project-img" />
</article>
)
}
After destructuring, you can refactor it to:
<Img fluid={image.childImageFluid.fluid} className="project-img" />
Another point, I guess that the gatsby-image import should be Img, not Image.

Accessing Current Page's Frontmatter -- Gatsby/React

Is it possible to access the current page's frontmatter through the default layout.
Using graphql, is there an option to filter a query based on the page's url?
.MDX Frontmatter
---
Title: 'About Us'
---
gatsby-config.js
{
resolve: 'gatsby-plugin-mdx',
options: {
defaultLayouts: {
pages: require.resolve('./src/layouts/default.js'),
},
},
}
default.js
<>
<header>?? Insert MDX Title ??</header>
<main></main>
<footer></footer>
</>
Even if you are generating pages from ./src/pages/ automatically using a default MDX template you still need to add the following to your plugins section in gatsby-config.js:
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages/`,
}
}
Now you can access the frontmatter with pageContext.frontmatter as mentioned in the documentation i.e.:
import React from 'react'
import Layout from './layout'
export default ({ children, pageContext }) => {
return (
<Layout>
<h1>{pageContext.frontmatter.title}</h1>
<article>{children}</article>
</Layout>
)
}
Yes there is. One way to step through it is to run gatsby develop and run your queries against the GraphiQL instance that gets ran. I have the title in my .mdx files and it is available through the allMdx and mdx schema. The Gatsby documentation here explains how to set up your component with the query.
{
allMdx {
edges {
node {
frontmatter {
title
}
}
}
}
mdx {
frontmatter {
title
}
}
}
MDX frontmatter is passed automatically along with children into the parent context.
{props.pageContext.frontmatter.Title}

Categories