Conditional rendering in React + Gatsby Static Query + GraphQL - javascript

I want to choose just featured posts (3 posts) from graphql for then show this in my blog page, so I limit query to only three results but in case that I just have one post the site will fail.
Because, I'm using a staticquery for get data, in this case I should to use render attribute in the staticquery component and I can not to use a if block on the attribute and when graphql won't find other posts it gonna fail.
Here the code:
featured-posts.js
import React from "react"
import MiniFeatured from "../components/minifeatured"
import { StaticQuery, Link, graphql } from "gatsby"
const Featured = () => {
return (
<StaticQuery
query={graphql`
query FeaturedBlogsport {
allMarkdownRemark (
limit: 3
sort: {order: DESC, fields: [frontmatter___date]}
filter: {frontmatter: {featured: {eq: true}}}
) {
edges {
node {
frontmatter {
title
description
post_image
}
fields {
slug
}
}
}
}
}
`}
render={data => (
<div className="mainBlogposts">
<div
className="featuredBlogpost"
style={{backgroundImage: `url('${data.allMarkdownRemark.edges[0].node.frontmatter.post_image}')`}}
>
<div className="featuredBlogpostContent">
<Link to={`https://strokequote.co/${data.allMarkdownRemark.edges[0].node.fields.slug}`}>
<h1 className="featuredBlogpostTitle">
{data.allMarkdownRemark.edges[0].node.frontmatter.title}
</h1>
<h6 className="featuredBlogpostAuthor">
{data.allMarkdownRemark.edges[0].node.frontmatter.description}
</h6>
</Link>
</div>
</div>
<div className="minifeaturedBlogpostsContainer">
<MiniFeatured
title={data.allMarkdownRemark.edges[1].node.frontmatter.title}
description={data.allMarkdownRemark.edges[1].node.frontmatter.description}
postImage={data.allMarkdownRemark.edges[1].node.frontmatter.post_image}
slug={data.allMarkdownRemark.edges[1].node.fields.slug}
/>
<MiniFeatured
title={data.allMarkdownRemark.edges[2].node.frontmatter.title}
description={data.allMarkdownRemark.edges[2].node.frontmatter.description}
postImage={data.allMarkdownRemark.edges[2].node.frontmatter.post_image}
slug={data.allMarkdownRemark.edges[2].node.fields.slug}
/>
</div>
</div>
)}
/>
)
}
export default Featured
PDD. Minifeatured are secondary featured posts in other components.
PDD 2. Sorry about my English, I'm still learning

I believe that find found a solution. With useStaticQuery from gatsby I can do something like this:
const Featured = () => {
const { edges } = FeaturedPostsQuery()
return (
<div className="mainBlogposts">
<div
className="featuredBlogpost"
style={{backgroundImage: `url('${edges[0].node.frontmatter.post_image}')`}}
>
<div className="featuredBlogpostContent">
<Link to={`https://strokequote.co/${edges[0].node.fields.slug}`}>
<h1 className="featuredBlogpostTitle">
{edges[0].node.frontmatter.title}
</h1>
<h6 className="featuredBlogpostAuthor">
{edges[0].node.frontmatter.description}
</h6>
</Link>
</div>
</div>
<div className="minifeaturedBlogpostsContainer">
<MiniFeatured
title={edges[1].node.frontmatter.title}
description={edges[1].node.frontmatter.description}
postImage={edges[1].node.frontmatter.post_image}
slug={edges[1].node.fields.slug}
/>
<MiniFeatured
title={edges[2].node.frontmatter.title}
description={edges[2].node.frontmatter.description}
postImage={edges[2].node.frontmatter.post_image}
slug={edges[2].node.fields.slug}
/>
</div>
</div>
)
}
export default Featured
export const FeaturedPostsQuery = () => {
const { allMarkdownRemark } = useStaticQuery(graphql`
query FeaturedBlogsport {
allMarkdownRemark (
limit: 3
sort: {order: DESC, fields: [frontmatter___date]}
filter: {frontmatter: {featured: {eq: true}}}
) {
edges {
node {
frontmatter {
title
description
post_image
}
fields {
slug
}
}
}
}
}
`)
return allMarkdownRemark
}

Why do you use a StaticQuery to achieve this? You can simply do it by using a regular GraphQL query, like:
import { graphql, Link } from 'gatsby';
import React from 'react';
const Featured = ({ data }) => {
return <div>
{data.allMarkdownRemark.edges.map(({ node: post }) => {
return <div className="mainBlogposts">
<div className="featuredBlogpost"
style={{ backgroundImage: `url(${post.frontmatter.post_image})` }}>
<div className="featuredBlogpostContent">
<Link to={`https://strokequote.co/${post.fields.slug}`}>
<h1 className="featuredBlogpostTitle">
{post.frontmatter.title}
</h1>
<h6 className="featuredBlogpostAuthor">
{post.frontmatter.description}
</h6>
</Link>
</div>
</div>
</div>;
})}
</div>;
};
export const AboutMeData = graphql`
query FeaturedBlogsport {
allMarkdownRemark (
limit: 3
sort: {order: DESC, fields: [frontmatter___date]}
filter: {frontmatter: {featured: {eq: true}}}
) {
edges {
node {
frontmatter {
title
description
post_image
}
fields {
slug
}
}
}
}
}
`;
What I've done is simply get all the 3 articles and loop through them, using your HTML structure. I aliased the node as a post using a destructuring inside the iterable variable in { node: post } but ideally, all that bunch of HTML should be another isolated component (it's really huge) and you should pass post as a prop to them but for now, it will work.
The snippet above will simply print the amount of post that the query can fetch, no matter if it's 1, 2, or 3.
Besides, it's cleaner than accessing manually each array position ([0], [1], etc).

Related

Why I can't get categories?

Does anyone know why I can't filter out the categories?
Error: Objects are not valid as a React child (found: object with keys {_key, _ref, _type}). If you meant to render a collection of children, use an array instead.
I also tried with {categories.title}
when implementing
{categories && categories.map((index, category) => (
<p key={index}>{category}</p>
))}
it just shows 0 in all cards.
import React from 'react'
import Link from 'next/link'
import groq from 'groq'
import sanityClient from '../client'
import imageUrlBuilder from '#sanity/image-url';
function urlFor (source) {
return imageUrlBuilder(sanityClient).image(source)
}
const stories = ({posts}) => {
return (
<div className='bg-gray-100'>
<div className='md:grid md:grid-cols-3 px-4 py-4'>
{posts.length > 0 && posts.map(
({ _id, title = '', slug = '', description, mainImage, categories }) =>
slug && (
<div key={_id} className='py-2 md:px-2'>
<Link href="/post/[slug]" as={`/post/${slug.current}`}>
<div className='border rounded-md p-4 bg-white cursor-pointer'>
<img className='w-full' src={urlFor(mainImage).url()}
width='500'
height='500'
alt="Mainn Image"/>
<h2 className='text-2xl py-6'>{title}</h2>
<p>{categories}</p>
<p className='opacity-60'>{description}</p>
</div>
</Link>
</div>
)
)}
</div>
</div>
)
}
export async function getStaticProps() {
const posts = await sanityClient.fetch(groq`
*[_type == "post" && publishedAt < now()] | order(publishedAt desc)
`)
return {
props: {
posts
}
}
}
export default stories
The main problem is in your code is related to built-in map method of Array constructor. map 's callback function as a first parameter gets an array value and as a second an index of that array item. So you mixed up the order of callback function parameters.
Also you used a category value as a children for JSX element (in your case it's <p>), whose type is probably an object.
For example, if your category looks like this: {id: 0, title: 'Books'}, then you can use as a child or an innerHTML/innerText of <p> like this:
<p key={category.id}>{category.title}</p>,
but not like:
<p key={index}>{category}</p>

Gatsby: getImage returns undefined

getImage is returning undefined so my GatsbyImage component is not rendered.
File structure:
src/pages/gallery.js
src/images (has 12 photos named photo-01.jpg, photo-02.jpg, ...)
I have the following code (gallery.js):
import React from "react";
import Layout from "../components/Layout";
import { useStaticQuery, graphql } from "gatsby";
import { GatsbyImage, getImage } from "gatsby-plugin-image";
const Gallery = () => {
const { gallery } = useStaticQuery(graphql`
query {
gallery: allFile(
filter: {
extension: { eq: "jpg" }
absolutePath: { regex: "/images/" }
}
) {
nodes {
id
childImageSharp {
fluid(maxWidth: 500, maxHeight: 500) {
...GatsbyImageSharpFluid_tracedSVG
}
}
}
}
}
`);
return (
<Layout>
<div className="container py-5">
<div className="row">
<div className="col-12">
<h1 className="text-gastby mb-4">Gallery</h1>
</div>
</div>
<div className="row">
{gallery.nodes.map((image) => (
<div className="col-lg-3 col-md-4 col-sm-6 mb-3" key={image.id}>
<GatsbyImage
image={getImage(image.childImageSharp.fluid)}
alt="Gallery"
/>
</div>
))}
</div>
</div>
</Layout>
);
};
export default Gallery;
what can i have wrong?
The problem is that you are mixing gatsby-image (from Gatsby 1 to Gatsby 2) and gatsby-plugin-image (from Gatsby 3 onwards). The first one is now deprecated.
This can be easily spotted when you query for fluid or fixed GraphQL nodes since they are created by gatsby-image which uses Img (from 'gatsby-image') component, which accepts a fluid or fixed property.
On the other hand, gatsby-plugin-image queries for gatsbyImageData (not fluid or fixed) and the corresponding GatsbyImage (from 'gatsby-plugin-image') component accepts an image property.
In your case, you are querying a gatsby-image structure applied in a gatsby-plugin-image component, that's why it is returning undefined.
Check the migration guide but essentially you will need to replace (and remove) all references to gatsby-image and install the required dependencies:
npm install gatsby-plugin-image gatsby-plugin-sharp gatsby-transformer-sharp
Add it to your gatsby-config.js:
module.exports = {
plugins: [
`gatsby-plugin-image`,
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
],
}
And change your query to something like:
const Gallery = () => {
const { gallery } = useStaticQuery(graphql`
query {
gallery: allFile(
filter: {
extension: { eq: "jpg" }
absolutePath: { regex: "/images/" }
}
) {
nodes {
id
childImageSharp {
gatsbyImageData(layout: FIXED)
}
}
}
}
`);
return (
<Layout>
<div className="container py-5">
<div className="row">
<div className="col-12">
<h1 className="text-gastby mb-4">Gallery</h1>
</div>
</div>
<div className="row">
{gallery.nodes.map((image) => (
<div className="col-lg-3 col-md-4 col-sm-6 mb-3" key={image.id}>
<GatsbyImage
image={getImage(image.childImageSharp)}
alt="Gallery"
/>
</div>
))}
</div>
</div>
</Layout>
);
};
export default Gallery;
Double-check the query since the nodes and the structure may be different when applying gatsby-plugin-image.
Note that getImage is a helper function and you may not need it, consider using directly:
<GatsbyImage
image={image.childImageSharp.gatsbyImageData}
alt="Gallery"
/>

Why does my TOC component add `{"-" + 1}` to its links every time I click on one of them?

I implemented a table-of-contents component to my site following this tutorial.
It works, but every time I click on one of the TOC's links, all the links (including the clicked one) in the TOC get a {"-" + 1} appended. So if I click a link, all those links go from #first-heading, #second-heading, etc. to #first-heading-1, #second-heading-1, etc. If I click one of the links again, they will all get #first-heading-2, #second-heading-2 etc., and so on. This behavior is of course problematic, as it breaks the links.
What's causing this? How do I fix it?
I noticed the tutorial uses the remark-slug plugin for the headings, while I use the gatsby-autolink-headers plugin. Can that be the source of the problem? I've not been able to test with the former, as I get an error when trying to install it.
EDIT: I've tried with both plugins. Same problem.
TableOfContents.js
import React from "react"
import Slugger from "github-slugger"
import { Link } from "gatsby"
const slugger = new Slugger()
export default ({ headings }) => (
<div className="table-of-contents">
<h3>On this page</h3>
<ol>
{headings
.filter(heading => heading.depth !== 1)
.map(heading => (
<li key={heading.value}>
<Link
to={"#" + slugger.slug(heading.value)}
>
{heading.value}
</Link>
</li>
))}
</ol>
</div>
)
post-template.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="article">
<h1 itemprop="headline">{post.frontmatter.title}</h1>
<p
className="lead"
itemprop="introduction"
>
{post.frontmatter.lead}
</p>
<MDXRenderer headings={post.headings}>
{post.body}
</MDXRenderer>
</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: "DD MMMM YYYY")
lead
}
headings {
depth
value
}
}
}
`
index.mdx
---
/* frontmatter */
---
<!-- component imported as shortcode in `layout.js` -->
<TableOfContents headings={props.headings} />
layout.js (excerpt)
import TableOfContents from "./article-components/TableOfContents"
const shortcodes = {
TableOfContents
}
export default function Layout({ children }) {
return (
<div className="layout-wrapper">
<Header />
<main>
<MDXProvider components={shortcodes}>
{children}
</MDXProvider>
</main>
</div>
)
}
It's because of the slugger. In their docs:
slugger.slug('foo')
// returns 'foo'
slugger.slug('foo')
// returns 'foo-1'
slugger.slug('bar')
// returns 'bar'
slugger.slug('foo')
// returns 'foo-2'
Because it ensures that the links are unique (like GitHub does), it appends the -1, -2, etc.
As long as you use you gatsby-autolink-headers plugin can get rid of the slugger implementation. If you need, you can use the normal link value (heading.value), the slug field (if provided), or sanitize it using a custom function like:
function slugify (text) {
return text
.toString()
.toLowerCase()
.normalize(`NFD`)
.trim()
.replace(/\s+/g, `-`)
.replace(/[^\w-]+/g, ``)
.replace(/--+/g, `-`);
};
<Link to={"#" + slugify(heading.value)}>

Next.js markdown-blog page code not working

import { Blogs } from "../../components/Data"
import Image from "next/image";
import Link from "next/link";
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
export default function index({ posts }) {
// const Short_Blog = Blogs.map(item =>
// <div className="BLOGS_Projects" key={item}>
// <div className="BLOGS_Projects_Image">
// <Image
// className='BLOGS_Projects_image'
// src={item['img-1']}
// layout='fill'
// // objectFit='contain'
// />
// </div>
// {/* if someone clicks on this link i want them to go to [project].js and send This item to [projcet].js */}
// <Link href={'/blogs/' + Blogs.indexOf(item)}>
// <a>{item['title']}</a>
// </Link>
// <p>{item['desc']}</p>
// </div>
// );
return (
<div className="BLOGS_Container">
<div className="BLOGS_Sub_Container">
<div className="BLOGS_New">
<h1 style={{ marginLeft: 25 + 'px' }}>Cyber-Security Blogs</h1>
<div className="BLOGS_Present">
{posts.map( post =>{
<h1 style={{zIndex: '10000'}}>{post}</h1>
})}
</div>
</div>
</div>
</div>
)
}
export async function getStaticProps() {
const files = fs.readdirSync(path.join('posts'))
const posts = files.map((filename) => {
const slug = filename.replace('.md', '')
const markdownWithMeta = fs.readFileSync(path.join('posts', filename), 'utf-8')
const { data: frontmatter } = matter(markdownWithMeta)
return {
slug,
frontmatter
}
})
return {
props: {
posts,
},
}
}
The posts object does exist but when I try to use it in the HTML, it doesn't show up anyone got any idea why this is happening?
the posts.map that I used is not giving any errors but it also doesn't show any h1 html in actual page, the actual page seems blank.
You need to return the element to render inside the callback of map function.
1st way
{posts.map((post) => {
return <h1 style={{ zIndex: "10000" }}>{post}</h1>;
})}
2nd way
{posts.map((post) => (
<h1 style={{ zIndex: "10000" }}>{post}</h1>
))}
React will not render plain html content as a string. You need to use the dangerouslySetInnerHTML prop, like so:
{
posts.map((post) => {
return <div dangerouslySetInnerHTML={{__html: post}}></div>
})
}
You can read more about it here:
https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
Alternatively you can use something like next-mdx-remote, which you can learn about here:
https://github.com/hashicorp/next-mdx-remote

Map inside map in react ( one is local array of images and the other is title from json )

As being designer and novice to react, I developed code in which local array is displaying as image and json data as title. Titles are working fine but images are not displaying and showing all of arrays in src attribute.
I have used Axios.get() to fetch data from the server.
I am missing out in logic somewhere while developing map inside map. I would be grateful for getting help.
EDIT : I want one image with one title.
CommonMeds.js
import React, { Component } from 'react';
import './CommonMeds.scss';
import MedSection from '../../Components/MedSection/MedSection';
import Axios from 'axios';
class CommonMeds extends Component {
state = {
MedTitle: [],
TitleImg: [
{ imageSrc: require('../../../../images/medstype_1.svg') },
{ imageSrc: require('../../../../images/medstype_2.svg') },
{ imageSrc: require('../../../../images/medstype_3.svg') },
{ imageSrc: require('../../../../images/medstype_4.svg') },
{ imageSrc: require('../../../../images/medstype_5.svg') },
{ imageSrc: require('../../../../images/medstype_6.svg') },
{ imageSrc: require('../../../../images/medstype_7.svg') },
{ imageSrc: require('../../../../images/medstype_8.svg') },
{ imageSrc: require('../../../../images/medstype_9.svg') },
{ imageSrc: require('../../../../images/medstype_10.svg') },
]
};
componentDidMount() {
const medInfo = Axios.get('URL OF JSON DATA');
medInfo.then( response => {
this.setState({MedTitle: response.data.result});
});
}
render() {
const Meds = this.state.MedTitle.map(med => {
const imglable = this.state.TitleImg.map(src => {
return src.imageSrc;
})
return <MedSection
Title={med.medicationCategory}
src = {imglable}
/>;
});
return(
<div className="container">
<h3 className="text-left">Common Medicines with Categories</h3>
<hr />
{Meds}
</div>
);
}
}
export default CommonMeds;
MedSection.js
import React from 'react';
import './MedSection.scss';
import MedicineList from '../MedicineList/MedicineList';
const MedSection = (props) => {
return (
<div className="col-12">
<div className="row">
<div className="col-12 col-md-3 col-lg-2 px-0">
<div className="c-medsimg-box py-4 px-2">
<img src={props.src} alt="Medication Type Icon" className="img-fluid" />
<h5 className="mt-3" key={props.key}>{props.Title}</h5>
</div>
</div>
<div className="col-12 col-md-9 col-lg-10">
<div className="h-100 d-flex align-items-center">
<ul className="c-medslist-ul pl-0 mb-0">
<MedicineList />
</ul>
</div>
</div>
</div>
<hr />
</div>
)
}
export default MedSection;
You are currently creating an array of images for each MedTitle. You could instead take the entry from TitleImage that has the same index as the current med in your loop instead.
You can also make it safe by using the % operator, so that if your MedTitle array is larger than your TitleImage array, you will still get an image.
const Meds = this.state.MedTitle.map((med, index) => {
const src = this.state.TitleImg[index % this.state.TitleImg.length].imageSrc;
return <MedSection Title={med.medicationCategory} src={src} />;
});
As long as length of both arrays are same, it will work.
Try using this.
const imglable = this.state.TitleImg.map(src => {
return src.imageSrc;
})
const Meds = this.state.MedTitle.map((med, index) => {
return <MedSection
Title={med.medicationCategory}
src = {imglable[index]}
/>;
});

Categories

Resources