gatsby-source-medium thumbnail image not showing - reactjs

I'm using gatsby in my react project, to show my medium, articles inside the project.
below is my graphql query for that.
const BlogPost = () => {
const blogMediumQueryData = useStaticQuery(graphql`
query Medium {
allMediumPost(sort: { fields: [createdAt], order: DESC }) {
edges {
node {
id
title
uniqueSlug
createdAt(formatString: "MMM YYYY")
virtuals {
previewImage {
imageId
}
}
author {
name
}
}
}
}
}
`)
const blogs = blogMediumQueryData.allMediumPost.edges
return (
<Blog
image={blog.node.virtuals.previewImage.imageId}
title={blog.node.title}
date={blog.node.createdAt}
author={blog.node.author.name}
path={blog.node.uniqueSlug}
/>
)
this gives me the preview image ID. And I'm passing it to the child component as a prop. But when I try to show the image with the Img component from gatsby, the Image is not showing.
Here is my code for the child component
import React from "react"
import { Link } from "gatsby"
import { slugify } from "../utils/utilityFunctions"
import Image from "../elements/image"
const Blog = ({ image }) => {
return (
<div className="content-block">
<div className="post-thubnail">
{image && (
<Link to={postUrl} target='blank'>
<Image src={image} alt={title} />
</Link>
)}
</div>
)
}
export default Blog
Here is the code for the Image component
import React from "react";
import Img from "gatsby-image";
const NonStretchedImage = props => {
let normalizedProps = props
normalizedProps = {...normalizedProps.fluid, aspectRatio: 1}
let alignment;
if(props.align === 'right'){
alignment = '0 0 0 auto'
} else if(props.align === 'left'){
alignment = '0 auto 0 0'
}else{
alignment = '0 auto'
}
if (props.fluid && props.fluid.presentationWidth) {
normalizedProps = {
...props,
style: {
...(props.style || {}),
maxWidth: props.fluid.presentationWidth,
margin: alignment,
},
}
}
return <Img {...normalizedProps} />
}
export default NonStretchedImage;
This is my first project with gatsby and graphql. Is there are anything that I have missed or is there anything that I'm doing wrong?
Thanks in advance

A few caveats that I guess will put you on the track to fix the issue.
node, in the GraphQL query is an array, in the same way, I guess that virtuals it is. Check and test the response in the localhost:8000/___graphql playground.
So assuming that your query works as expected, your code should look like:
const BlogPost = () => {
const blogMediumQueryData = useStaticQuery(graphql`
query Medium {
allMediumPost(sort: { fields: [createdAt], order: DESC }) {
edges {
node {
id
title
uniqueSlug
createdAt(formatString: "MMM YYYY")
virtuals {
previewImage {
imageId
}
}
author {
name
}
}
}
}
}
`)
const blogs = blogMediumQueryData.allMediumPost.edges
return (
<Blog
image={blog.node[0].virtuals.previewImage.imageId}
title={blog.node[0].title}
date={blog.node[0].createdAt}
author={blog.node[0].author.name}
path={blog.node[0].uniqueSlug}
/>
)
Alternatively, you can loop through the array of nodes and use your previous Blog component since it will get each iterable variable.
I don't think your Image component be able to render a gatsby-image only using the imageId. Gatsby needs a bunch of data (given by its transformers and sharps) to render the image, not using an identifier but series of fields (that's why it usually renders query fragments, noted by ...). Your image component, in the end, should render something like:
<img src={`https://medium.com/${blog.node[0].virtuals.previewImage.imageId}`}
Based on: https://blog.devgenius.io/how-to-scrap-your-medium-articles-with-gatsby-js-f35535ebc09d
So summarizing, gatsby-source-medium by itself doesn't provide enough data to use gatsby-image or gatsby-image-plugin plugins so I'm afraid you won't be able to use the Img component. You have to use the standard img tag.

Related

Windowing in React/Gatsby/Chakra UI

I have a simple web app using React, Gatsby and Chakra UI frameworks. The app consists of an index page which queries frontmatter from 1000+ mdx files and renders a minimal summary component with 5 fields from the frontmatter and a link to a detail page for each. The 1000+ detail pages are generated in gatsby-node.js using createPage.
The index page uses map to iterate through each of the mdx nodes and uses a Chakra UI simple grid along with some other Chakra components for each item.
The lighthouse report received when the app is deployed to Gatsby Cloud rates the app 60/100 for performance largely due to excessive elements in the DOM (the 1000+ summary elements rendered by the index page).
I’ve reviewed all of the related documentation and searched SO among other sources but can find no feasible solution to rendering only the html for the 25 or so items that are displayed on screen at any given point and rendering the rest as needed rather than rendering all 1000+ from the outset.
import * as React from "react";
import { ChakraProvider, chakra, Box, SimpleGrid, HStack, Button, VStack, Wrap, WrapItem, Badge } from "#chakra-ui/react";
import { graphql, useStaticQuery } from 'gatsby';
const IndexPage = () => {
const query = useStaticQuery(graphql`
query AllObjects {
allMdx(sort: {fields: frontmatter___field1}) {
nodes {
frontmatter {
field1
field2
field3
field4
field5
field6
field7
uniqueId
}
}
}
}
`)
return (
<ChakraProvider>
<body>
<main>
<SimpleGrid columns={{base: 1, lg: 3, md: 2, sm:1}} spacing={{base: '1.5em', lg: '1.5em', md:'1.0', sm:'0.90em'}}>
{ query.allMdx.nodes.map((node) => (
<Box key={node.frontmatter.field1} margin="2em" padding="1em">
<HStack padding="0.4em" align="center" alignItems="stretch" justifyContent="space-between">
<Button size="sm" shadow="md" colorScheme="blue"
onClick={(e) => {
e.preventDefault();
window.location.href=`/objects/${node.frontmatter.field1.toLowerCase()}`;
}}
>Detail</Button>
<Box align="center"/>
<VStack alignItems="end" justifyContent="right">
<Wrap columns={2} spacing={1} direction={["row-reverse"]} isInline="true" shouldWrapChildren="true">
<WrapItem>
{node.frontmatter.field5 === true &&
<Badge colorScheme="green">Field5</Badge>
}
</WrapItem>
<WrapItem>
{node.frontmatter.field6 === true &&
<Badge colorScheme="blue">Field6</Badge>
}
</WrapItem>
<WrapItem>
{node.frontmatter.field7 === true &&
<Badge colorScheme="orange">field7</Badge>
}
</WrapItem>
<WrapItem>
{node.frontmatter.field4 === true &&
<Badge colorScheme="red">Field8</Badge>
}
</WrapItem>
</Wrap>
</VStack>
</HStack>
<Box bg="gray.300" borderRadius="0.5em" margin="0em" padding="0em">
<chakra.h2 id={node.frontmatter.field1.toLowerCase()}>
Field1: {node.frontmatter.field1}
</chakra.h2>
<chakra.p>Field2: {node.frontmatter.field2}</chakra.p>
<chakra.p>{node.frontmatter.field3}</chakra.p>
</Box>
</Box>
))}
</SimpleGrid>
</main>
</body>
</ChakraProvider>
)
};
export default IndexPage;
Well, you spot the solution. Use an infinite scroll or some similar delayed (button, etc) approach to render the full amount of grid items on-demand, rather than all of them at the same time.
Just create a state (useState) that contains the sliced amount of elements and upgrade them as soon as the user scrolls the page. That will save (and delay) your initial DOM elements.
I will add a button-based approach to render more elements but the idea is exactly the same using an infinite scroll.
const IndexPage = () => {
const query = useStaticQuery(graphql`
query AllObjects {
allMdx(sort: {fields: frontmatter___field1}) {
nodes {
frontmatter {
field1
field2
field3
field4
field5
field6
field7
uniqueId
}
}
}
}
`)
// Array of all news articles
const allGridElements = query.allMdx.nodes
// State for the list
const [list, setList] = useState([...allGridElements.slice(0, 10)])
// State to trigger the load more
const [loadMore, setLoadMore] = useState(false)
// State of whether there is more to load
const [hasMore, setHasMore] = useState(allGridElements.length > 10)
// Load more button click
const handleLoadMore = () => {
setLoadMore(true)
}
// Handle loading more articles
useEffect(() => {
if (loadMore && hasMore) {
const currentLength = list.length
const isMore = currentLength < allGridElements.length
const nextResults = isMore
? allGridElements.slice(currentLength, currentLength + 10)
: []
setList([...list, ...nextResults])
setLoadMore(false)
}
}, [loadMore, hasMore]) //eslint-disable-line
//Check if there is more
useEffect(() => {
const isMore = list.length < allGridElements.length
setHasMore(isMore)
}, [list]) //eslint-disable-line
return (
<div>
<h1>Load more demo</h1>
<div>
{list.map((item) => (
{ /* Your JSX rendering the grid items */ }
))}
</div>
{hasMore ? (
<button onClick={handleLoadMore}>Load More</button>
) : (
<p>No more results</p>
)}
</div>
)
}
export default IndexPage
Note: to avoid a neverending answer I omitted the JSX returned by your loop. Just place it in the comment.
It's quite self explanatory, you set all your elements in a React state (useState) and iterate through it. The useEffect is in charge of upgrade the list based on a listener.
Other useful resources:
https://scotch.io/tutorials/build-an-infinite-scroll-image-gallery-with-gatsby-and-netlify-functions
https://www.gatsbyjs.com/starters/baobabKoodaa/gatsby-starter-infinite-scroll

Gatsby GraphQL Variables In Component

I'm an iOS developer and I've been struggling for what seems like the longest time making my portfolio site from scratch. I've tried a bunch of different technologies and have finally settled on using Gatsby to create it.
So far things have been fairly straightforward but I can not figure out for the life of me how to get a component that looks like the picture below. I've gotten most of the layout design working, but I can't seem to use graphql to query the images I need in the component.
Desired Layout
I've found plenty of Gatsby example templates such as this one and this one that are similar. However the main difference is that each of these only have one image and they seem to be using Gatsby 2.0 instead of 3.0.
I can get one image using "useStaticQuery", however I need access to different images for each component. From my understanding this is not possible to do within a component, only on a page. I also can not pass the image path as a variable to StaticImage either.
export default function App(props) {
const query = useStaticQuery(graphql`
query AppSectionImages {
icon: file(relativePath: { eq: "EzMaxRequest/AppIcon_180.png" }) {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
`);
const image = getImage(query.icon);
const app = props.app;
return (
<div>
<h1>{app.title}</h1>
<GatsbyImage image={image} />
</div>
);
Result
Can anyone please explain to me how I can get the desired layout in a component?
Edit
Here is some relevant code of what I am doing.
This is my index.js home page.
export default function IndexPage({ data }) {
const projects = data.apps.edges;
return (
<Layout>
<SEO title="Home" />
<HeroSection />
<DescriptionSection />
<div>
{projects.map(({ node: project }) => (
<AppSection app={project} />
))}
</div>
<FooterSection />
</Layout>
);
}
//export page query
export const query = graphql`
query Apps {
apps: allAppsJson(sort: { order: ASC, fields: order }) {
edges {
node {
appLink
title
tagline
moreLink
order
icon
}
}
}
}
`;
Here is the component.
export default function App(props) {
const query = useStaticQuery(graphql`
query AppSectionImages {
icon: file(relativePath: { eq: "EzMaxRequest/AppIcon_180.png" }) {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
`);
const image = getImage(query.icon);
const app = props.app;
return (
<div>
<h1>{app.title}</h1>
<GatsbyImage image={image} alt={app.title} />
</div>
);
}
You have a few options:
Query for all of your image data in your page query and prop-drill the data to the component that uses it to display the image.
Using Gatsby v3+, hardcode the image references for each component using the new StaticImage component.
If you have a single component used multiple times with different content/images, but a static parent component with your content, you can leverage option #2 above but pass the image component down as a prop or children.

There's not a page yet at /second%20post

I have an issue regarding my gatsby site. I am fetching content from contentful and according to the code in my gatsby-node.js it has to generate two pages and it does but only one of them is working when I click on it for the second one it show that
There's not a page yet at /second%20post
I am so confused cause most of the issues asked here telling that they are not able to generate the pages and I don't know if the page is created or not and if it is created then why it shows me the error message and also when got to the error page the
second post
link is given but it is non clickable. all the other code is in my git repository here at Github code
Pleas refer to image for clear understanding at
Image here
here is my gatsby-node.js code file
const path = require(`path`)
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions;
// Define a template for blog post
const blogPost = path.resolve(`./src/templates/blog-post-contentful.js`)
// Get all markdown blog posts sorted by date
const result = await graphql(
`
{
allContentfulBlockchainlearning{
edges{
node{
slug
title
subtitle
}
}
}
}
`
)
if (result.errors) {
reporter.panicOnBuild(
`There was an error loading your blog posts`,
result.errors
)
return
}
const posts = result.data.allContentfulBlockchainlearning.edges
// Create blog posts pages
// But only if there's at least one markdown file found at "content/blog" (defined in gatsby-
config.js)
// `context` is available in the template as a prop and as a variable in GraphQL
if (posts.length > 0) {
posts.forEach((post, index) => {
const previousPostSlug = index === 0 ? null : posts[index - 1].id
const $nextPostSlug = index === posts.length - 1 ? null : posts[index + 1].id
createPage({
path: post.node.slug,
component: blogPost,
context: {
slug: post.node.slug,
previousPostSlug,
$nextPostSlug,
},
})
})
}
}
and here is my blog-post template I want to creat
import React from "react"
import { Link, graphql } from "gatsby"
import Bio from "../components/bio"
import Layout from "../components/layout"
import SEO from "../components/seo"
const BlogPostTemplate = ({ data, location }) => {
const post = data.contentfulBlockchainlearning
const siteTitle = data.site.siteMetadata?.title || `Title`
const { previous, next } = data
return (
<Layout location={location} title={siteTitle}>
<SEO
title={post.title}
description={post.subtitle}
/>
<article
className="blog-post"
itemScope
itemType="http://schema.org/Article"
>
<header>
<h1 itemProp="headline">{post.title}</h1>
<p>{post.date}</p>
</header>
<section
dangerouslySetInnerHTML={{ __html: post.content.raw }}
itemProp="articleBody"
/>
<hr />
<footer>
<Bio />
</footer>
</article>
<nav className="blog-post-nav">
<ul
style={{
display: `flex`,
flexWrap: `wrap`,
justifyContent: `space-between`,
listStyle: `none`,
padding: 0,
}}
>
<li>
{previous && (
<Link to={previous.slug} rel="prev">Hey There
← {previous.title}
</Link>
)}
</li>
<li>
{next && (
<Link to={next.slug} rel="next">
{next.title} →
</Link>
)}
</li>
</ul>
</nav>
</Layout>
)
}
export default BlogPostTemplate
export const pageQuery = graphql`
query BlogPostBySlug(
$slug: String!
$previousPostSlug: String
$nextPostSlug: String
) {
site {
siteMetadata {
title
}
}
contentfulBlockchainlearning(slug: {eq: $slug}){
title
subtitle
content{
raw
}
}
previous: contentfulBlockchainlearning(slug: { eq: $previousPostSlug}) {
title
}
next: contentfulBlockchainlearning(slug: { eq: $nextPostSlug }) {
title
}
}
`
The issue is simple, you can't create a URL with whitespace like the one you are trying to create. second page should be parsed as second-page since the whitespace between second and page potentially will cause a lot of issues.
Gatsby is creating properly the pages since they appear on the 404 page (under gatsby develop, the 404 page lists all you created pages). However, it doesn't have a valid route because your slugs must be slugified. Ideally, the slug should be fetched with the correct format from the CMS already, however, you can add some controls to avoid this behaviour:
if (posts) {
posts.forEach((post, index) => {
let slugifiedPath= post.node.slug.toLowerCase().replace(/\s/g, '-');
const previousPostSlug = index === 0 ? null : posts[index - 1].id
const $nextPostSlug = index === posts.length - 1 ? null : posts[index + 1].id
createPage({
path: slugifiedPath,
component: blogPost,
context: {
slug: post.node.slug,
previousPostSlug,
$nextPostSlug,
},
})
})
}
It's quite self-explanatory but, since your paths are being fetched with a wrong format, you need to refactor them by:
let slugifiedPath= post.node.slug.toLowerCase().replace(/\s/g, '-');
It transforms it to lower case and it replaces all-white spaces globally (/\s/g) using a regular expression for hyphens (-), creating a valid slug.

Getting data Gatsby.js and contentful

I have pages in contenful with different URLs. Now I'm getting all data from all pages, but I need to get different data for different URL. I'm tryin to filter it, but get error. So How I can check if url='something' I need query it ?
import React from "react";
import { StaticQuery, graphql } from "gatsby";
import ArticleMfo from "../components/articleMfo";
const Products = () => (
<StaticQuery
query={graphql`
query MyQuery {
allContentfulAllPages(filter: {link: {eq: $MYURL}}) {
edges {
node {
mfo {
__typename
... on ContentfulBank {
id
text
limit
rate
term
link
logo {
title
file {
url
}
}
}
}
}
}
}
}
`}
render={data => (
<Container className="container">
{data.allContentfulAllPages.edges.map(({ node }, i) => (
<div>
{node.mfo.map(mfos => (
<ArticleMfo key={mfos.id} content={mfos} />
))}
</div>
))}
</Container>
)}
/>
);
export default Products
Static query (hence the name) does not accept variables. As you can see from the Static Query docs:
StaticQuery does not accept variables (hence the name “static”), but
can be used in any component, including pages
If you want to filter it, you will need to use a page query and pass the variable name (MYURL) via context on each page. In that case, you'll need to move your query to gatsby-node.js and, on every page creation, pass the variable through context to make it available to use as a filter. Something like:
const path = require("path")
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
const result = await graphql(
`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
frontmatter {
path
}
}
}
}
}
`
)
// Handle errors
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
const blogPostTemplate = path.resolve(`src/templates/blog-post.js`)
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
const path = node.frontmatter.path
createPage({
path,
component: blogPostTemplate,
// In your blog post template's graphql query, you can use pagePath
// as a GraphQL variable to query for data from the markdown file.
context: {
pagePath: path,
},
})
})
}
Note: Replace the query above and the resolvers for your data.
With the snippet above, every page created from the GraphQL query will have the path available (as pagePath) through context to filter, adapt it to your needs.

Mapping Data Returning Same Value - Gatsby

I'm mapping over some data (3 image slides i have stored in a headless cms). I am passing the retuned data into slide component, and from there its being passed down to a button component, which is just a 'gatsby-link'. The mapped data contains a "page_link", among other things that should be different for each slide. However, for some reason the same "page_link" is being passed down to each button, even though they should all be different. I am logging my data in the console and can confirm that each array i am mapping over contains different data. I get the different image, title, text, etc fields for each slide, but i am recieving the same "page_link" for each slide. I am confused as to what i am doing wrong.
Heres my code:
GraphQl Query:
const HOMEPAGE_DATA = graphql`
query {
prismicHomePage {
data {
body {
slider {
title {
text
}
text {
text
}
page_link{
text
}
button_text{
text
}
image {
localFile {
childImageSharp {
fluid(maxWidth: 1280, quality: 90) {
...GatsbyImageSharpFluid_withWebp
}
}
}
}
}
}
}
}
`
// get homepage data
const data = useStaticQuery(HOMEPAGE_DATA)
// simplify data
const home_data = data.prismicHomePage.data
Mapped Data:
{home_data.slider.map((slide, index) => {
console.log(slide)
return (
<Slide
key={slide.title.text}
title={slide.title.text}
image={slide.image.localFile.childImageSharp.fluid}
text={slide.text.text}
button_text={slide.button_text.text}
path_link={slide.page_link.text}
/>
)
})}
Slide Component:
const Slide = props => {
return (
<StyledBackgroundImage fluid={props.image} alt={`${props.title}`}>
<Wrapper>
<SlideContent>
<h1>{props.title}</h1>
<p>{props.text}</p>
<div className="slide-btn">
<Button path={props.path_link} text={props.button_text} />
</div>
</SlideContent>
</Wrapper>
</StyledBackgroundImage>
)
}
Button Component:
The is just a Styled "Link" Component from Gatsby.
const Button = props => {
return (
<StyledButton to={props.path} title={`${props.text}`}>
{props.text}
</StyledButton>
)
}

Resources