How to use data from GraphQL in a React component in GatsbyJS - graphql

I'm trying to query for a specific category title and returning it inside a div. It gives me the following error message:
TypeError: can't access property "node", data.edges is undefined
This is my file:
import React from "react"
import { graphql, useStaticQuery } from "gatsby"
import styled from "styled-components"
import { H3, BodyMain } from "../styles/TextStyles"
export default function CategorySection() {
const data = useStaticQuery(graphql`
query categoryQuery {
allGraphCmsCategory(filter: { title: { eq: "CSS" } }) {
edges {
node {
title
slug
description
}
}
}
}
`)
return (
<Wrapper>
<ContentWrapper>
<TextWrapper>
<Title>Browse by Categories</Title>
<Description>
Use the category tags to narrow down what you are looking for.
</Description>
</TextWrapper>
<CategoryWrapper>
<Categories>{data.edges.node.title}</Categories>
</CategoryWrapper>
</ContentWrapper>
</Wrapper>
)
}
const Wrapper = styled.div``
const ContentWrapper = styled.div``
const TextWrapper = styled.div``
const Title = styled(H3)``
const Description = styled(BodyMain)``
const CategoryWrapper = styled.div``
const Categories = styled.div``
I believe my query is right, as I'm able to see results on http://localhost:8000/___graphql
When I have tested it and see it work, I would like to map through all categories and create separate pages for each.
Can you guide me in the right direction?

Your query looks good, however, you need to access the nested object as your GraphQL shows, in your case, this should work:
export default function CategorySection() {
const data = useStaticQuery(graphql`
query categoryQuery {
allGraphCmsCategory(filter: { title: { eq: "CSS" } }) {
edges {
node {
title
slug
description
}
}
}
}
`)
console.log("your data is", data.allGraphCmsCategory.edges) // use to access to the nested data data.allGraphCmsCategory.edges[0].node.title
return (
<Wrapper>
<ContentWrapper>
<TextWrapper>
<Title>Browse by Categories</Title>
<Description>
Use the category tags to narrow down what you are looking for.
</Description>
</TextWrapper>
<CategoryWrapper>
<Categories>{data.edges.node.title}</Categories>
</CategoryWrapper>
</ContentWrapper>
</Wrapper>
)
}
Note that, inside data, you first need to access allGraphCmsCategory and keep following the object tree. I've assumed (because of the all keyword in allGraphCmsCategory) that the result will have multiple edges nodes (array), that's why the edges[0].
Alternatively, you can use the StaticVersion component:
export default function CategorySection() {
return (
<StaticQuery
query={graphql`
query categoryQuery {
allGraphCmsCategory(filter: { title: { eq: "CSS" } }) {
edges {
node {
title
slug
description
}
}
}
}
`}
render={data => {
console.log(data);
return (
<Wrapper>
<ContentWrapper>
<TextWrapper>
<Title>Browse by Categories</Title>
<Description>
Use the category tags to narrow down what you are looking for.
</Description>
</TextWrapper>
<CategoryWrapper>
<Categories>{data.allGraphCmsCategory.edges[0].node.title}</Categories>
</CategoryWrapper>
</ContentWrapper>
</Wrapper>
)
}}
/>
)
}
To make it dynamic:
{data.allGraphCmsCategory.edges.map(item=>{
return <Categories>{item.title}</Categories>
})}

Related

Gatsbyjs + Directus how to get image from content?

I connected gatsby and directus. everything is ok, but...
I have html text field with name "content" in directus, it has images inside. how do i get them in gatsby?
If you want to display your image inside the raw HTML (content) you will need to use dangerouslySetInnerHTML or use a markdown parser. The imageFile node can be isolated and print alone.
You just need to use a page query to fetch your data like:
import * as React from 'react'
import { graphql } from 'gatsby'
import Markdown from 'markdown-to-jsx';
const HomePage = ({data}) => {
console.log("Your data is", data);
return (
<div>
Your image is: <img src={data.article.image.imageFile.publicURL} alt="" />
Your content is:
<Markdown>{data.article.content}</Markdown>
</div>
)
}
export const query = graphql`
query directus{
article {
id
title
description
content
image {
imageFile {
publicURL
}
}
}
}
`
export default HomePage
Note: I've based my query in your GraphQL schema, tweak it to adapt it to yours.
Your queried data is stored inside props.data (destructured as data in this case), then you just need to get the needed node in each case. For your image, you need to keep nesting the object until your reach publicURL.
Regarding the rest of the content, I've used markdown-to-jsx library which is quite straightforward to use, but you can omit it and use directly dangerouslySetInnerHTML or another library.
I solved the problem as follows:
I created a multiplefiles field in Directus in the Article named "imagelist" there indicated the same images that I use in the article.
Now gatsby gives me an array of images in GraphQL.
Wrote a CustomImage component for the image, where I check the id.
Used the overrides option to replace the img tag in the Markdown component.
So, my code looks that:
layout props:
interface ArticleImage {
directus_files_id: {
id: string;
imageFile: IGatsbyImageData;
}
}
interface BlogLayoutProps {
title: string;
annotation: string;
imageData: IGatsbyImageData;
allImages: Array<ArticleImage>;
content: string;
}
custom image component:
const CustomImage = (props) => {
const { alt, width, height, src } = props;
const image = allImages.find(i => src.indexOf(i.directus_files_id.id) !== -1);
if (image) {
const imgData = getImage(image.directus_files_id.imageFile);
imgData.width = +width;
imgData.height = +height;
return <GatsbyImage image={imgData} alt={alt}/>;
}
return <img src={src} width={width} height={height} alt={alt} />
};
in layout component:
<StyledContent>
<Markdown
options={{
overrides: {
img: {
component: CustomImage,
}
}
}}
>
{content}
</Markdown>
</StyledContent>
query in page:
query MyQuery {
directus {
article_by_id(id: "26434049-7bb4-46d3-8ad1-c04973b9cf71") {
id
category {
id
title
}
title
description
content
image {
id
width
height
imageFile {
childImageSharp {
gatsbyImageData
}
}
}
imagelist {
directus_files_id {
id
imageFile {
childImageSharp {
gatsbyImageData
}
}
}
}
}
}
}
I hope someone will help my solution or push him to new thoughts!

In Gatsby how do I pass a relative path file to a template that is also pulling a query?

In Gatsby I've coded a single post template:
singlePost.js:
import React from 'react'
import { graphql } from 'gatsby'
import { MDXRenderer } from 'gatsby-plugin-mdx'
import { H1 } from '../elements'
import { Container, Post, FeatureImage } from '../components'
const singlePost = ({ data }) => {
const featureImage = data.mdx.frontmatter.featureImg.childImageSharp.fixed
return (
<Container>
<FeatureImage fixed={featureImage} />
<Post>
<H1 margin=" 0 0 2rem 0">{data.mdx.frontmatter.title}</H1>
<MDXRenderer>{data.mdx.body}</MDXRenderer>
</Post>
</Container>
)
}
export const pageQuery = graphql`
query SinglePostQuery($id: String!) {
mdx(id: { eq: $id }) {
body
frontmatter {
date
excerpt
featureImg {
childImageSharp {
fixed(width: 1920) {
...GatsbyImageSharpFixed
}
}
}
title
slug
}
}
}
`
export default singlePost
In my gatsby-node.js I get the data from the slug:
data.allMdx.edges.map(edge => {
const slug = edge.node.frontmatter.slug
const id = edge.node.id
actions.createPage({
path: slug,
component: require.resolve(`./src/templates/singlePost.js`),
context: { id },
})
})
In the frontmatter of the markdown file there is a feature image:
---
title: Bacon Ipsum
slug: bacon
date: 2021-02-09
featureImg: nature.jpg
excerpt: Bacon ipsum dolor amet pastrami prosciutto meatball fatback, andouille drumstick shank burgdoggen brisket cow turkey.
---
if the post markdown file doesn't have an image I get an error of:
Cannot read property 'childImageSharp' of null
I can access the default image I've set with:
const defaultImage = useStaticQuery(graphql`
query {
default: file(relativePath: { eq: "default.jpg" }) {
publicURL
}
}
`)
but when I try to query for the content and the default with:
const defaultImage = useStaticQuery(graphql`
query SinglePostQuery($id: String!) {
mdx(id: { eq: $id }) {
body
frontmatter {
date
excerpt
featureImg {
childImageSharp {
fixed(width: 1920) {
...GatsbyImageSharpFixed
}
}
}
title
slug
}
}
default: file(relativePath: { eq: "default.jpg" }) {
publicURL
}
}
`)
I get an error of:
If you're not using a page query but a useStaticQuery / StaticQuery
you see this error because they currently don't support variables.
In Gatsby how can I query the slug but also have pass the default image to the Feature Image component?
As the error log points, useStaticQuery (because it's a type of static query) doesn't accept variables, hence the name.
In Gatsby how can I query the slug but also have pass the default
image to the Feature Image component?
You can use the same context where you attach the id to pass the image while using page queries.
data.allMdx.edges.map(edge => {
const slug = edge.node.frontmatter.slug
const id = edge.node.id
const imagePath = edge.node.featureImg || 'default.jpg'
actions.createPage({
path: slug,
component: require.resolve(`./src/templates/singlePost.js`),
context: {
id
imagePath
},
})
})
Note: You may need to query the image in your gatsby-node.js. Change the imagePath to another identifier that matches better your data structure if needed.
Then, in your template:
import React from 'react'
import { graphql } from 'gatsby'
import { MDXRenderer } from 'gatsby-plugin-mdx'
import { H1 } from '../elements'
import { Container, Post, FeatureImage } from '../components'
const singlePost = ({ data }) => {
const featureImage = data.mdx.frontmatter.featureImg.childImageSharp.fixed
return (
<Container>
<FeatureImage fixed={featureImage} />
<Post>
<H1 margin=" 0 0 2rem 0">{data.mdx.frontmatter.title}</H1>
<MDXRenderer>{data.mdx.body}</MDXRenderer>
</Post>
</Container>
)
}
export const pageQuery = graphql`
query SinglePostQuery($id: String!, $imagePath: String) {
mdx(id: { eq: $id }) {
body
frontmatter {
date
excerpt
featureImg {
childImageSharp {
fixed(width: 1920) {
...GatsbyImageSharpFixed
}
}
}
title
slug
}
}
default: file(relativePath: { eq: $imagePath }) {
publicURL
}
}
`
export default singlePost
Note: add the $imagePath as a nullable value (by removing the exclamation mark, !), since, as you said, not all the posts will have it.
Try removing the featureImg block if it stills breaking the query. Because of:
const imagePath = edge.node.featureImg || 'default.jpg'
Your imagePath variable will always contain the needed data,or your featureImg, or your default one. The key is to make separate queries.

Gatsby Contentful embedded image

As I see there is no json option anymore when querying the contentfulBlogPost only raw. I was able to make some changes to get everything from the body, except the image from that post.
If I made a test in GraphQL Playground I can get the image id and url but that's it.
query {
allContentfulAsset {
edges {
node {
id
file {
url
}
}
}
}
}
I tried to find an example how to get embedded images but no luck....
import React from 'react'
import { graphql } from 'gatsby'
import { documentToReactComponents } from '#contentful/rich-text-react-renderer'
import Layout from '../components/layout'
export const query = graphql`
query($slug: String!) {
contentfulBlogPost(slug: {eq: $slug}) {
title
publishedDate(formatString: "MMMM Do, YYYY")
body {
raw
}
}
allContentfulAsset {
edges {
node {
id
file {
url
}
}
}
}
}
`
const Blog = (props) => {
const options = {
renderNode: {
"embedded-asset-block": (node) => {
const alt = node.data.title
const url = node.file.url
return <img alt={alt} src={url}/>
}
}
}
return (
<Layout>
<h1>{props.data.contentfulBlogPost.title}</h1>
<p>{props.data.contentfulBlogPost.publishedDate}</p>
{documentToReactComponents(JSON.parse(props.data.contentfulBlogPost.body.raw, options))}
</Layout>
)
}
export default Blog
Plugins:
...
'gatsby-plugin-sharp',
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
'gatsby-remark-relative-images',
{
resolve: 'gatsby-remark-images-contentful',
options: {
maxWidth: 750,
linkImagesToOriginal: false
}
}
]
}
}
],
}
Hi I saw this solution in a Youtube comment. First thing you have to do is change your Graphql query to something like this:
query ($slug: String!) {
contentfulBlogPost(slug: {eq: $slug}) {
id
title
publishedDate(formatString: "MMMM Do, YYYY")
body {
raw
references {
... on ContentfulAsset {
contentful_id
title
file {
url
}
}
}
}
}
}
Then change your options constant to:
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ASSET]: node => {
console.log(node);
const imageID = node.data.target.sys.id;
const {
file: {url},
title
} = props.data.contentfulBlogPost.body.references.find(({contentful_id: id}) => id === imageID);
return <img src={url} alt={title} />
}
}
}
Use something like:
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
​
const Bold = ({ children }) => <span className="bold">{children}</span>
const Text = ({ children }) => <p className="align-center">{children}</p>
​
const options = {
renderMark: {
[MARKS.BOLD]: text => <Bold>{text}</Bold>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
[BLOCKS.EMBEDDED_ASSET]: node => {
return (
<>
<h2>Embedded Asset</h2>
<pre>
<code>{JSON.stringify(node, null, 2)}</code>
</pre>
</>
)
},
},
}
​
renderRichText(node.bodyRichText, options)
Source: https://www.contentful.com/developers/docs/tutorials/general/rich-text-and-gatsby/
The return statement in BLOCKS.EMBEDDED_ASSET entry will contain your data, adapt to your needs. If you go inside the dependency, you'll see all the exposed methods, so you will have also a BLOCKS.EMBEDDED_ENTRY entry for your embedded entries. Apply it like:
[BLOCKS.EMBEDDED_ENTRY]: node => {
// your logic to manipulate the entry here
return (
<>
<div>whatever</div>
</>
)
},
For anyone that is still struggling to find the "references" field in graphql, remember that you HAVE TO first create an entry in contentful by adding at least one image. Otherwise, the references field will not show up in graphql, hence you can not query it.

How to add variables to graphQL query?

I am using Gatsby.
I need to increase the limit of 3 to each onCLick.
I've tried to follow this post, but with no success, so I removed the edits, and here is the original code...
This will help me to load more posts.
export const LatestNews = ({data}) => {
console.log(data);
return (
<section id="news_posts_section">
<p>some data</p>
</section>
);
};
export const latestNewsQuery = graphql`
query myquery{
allMarkdownRemark(
filter: { frontmatter: { layout: { eq: "news" } } }
sort: { fields: frontmatter___date, order: DESC }
limit: 2
) {
nodes {
frontmatter {
layout
title
path
date
featuredImage
thumbnail
}
excerpt(pruneLength: 325)
}
}
}
`;
Here is my gatsby-node:
exports.createPages = ({ actions, graphql }) => {
const { createPage } = actions;
const blogPostTemplate = path.resolve('src/templates/blog-post/BlogPost.js');
const newsTemplate = path.resolve('src/templates/news-single/NewsSingle.js');
const latestNewsPage = path.resolve(
'src/components/pages-implementation/news/sections/LatestNews.js',
);
const blog = graphql(`
{
blog: allMarkdownRemark(
filter: { frontmatter: { layout: { eq: "blog" } } }
) {
edges {
node {
frontmatter {
path
}
}
}
}
}
`).then((result) => {
if (result.errors) {
result.errors.forEach((e) => console.error(e.toString()));
return Promise.reject(result.errors);
}
const posts = result.data.blog.edges;
posts.forEach((edge) => {
const { path } = edge.node.frontmatter;
createPage({
path: path,
component: blogPostTemplate,
context: {},
});
});
});
const news = graphql(`
{
news: allMarkdownRemark(
filter: { frontmatter: { layout: { eq: "news" } } }
) {
edges {
node {
frontmatter {
path
}
}
}
}
}
`).then((result) => {
if (result.errors) {
result.errors.forEach((e) => console.error(e.toString()));
return Promise.reject(result.errors);
}
const news = result.data.news.edges;
news.forEach((edge) => {
const { path } = edge.node.frontmatter;
createPage({
path: path,
component: newsTemplate,
context: {},
});
});
news.forEach(edge => {
createPage({
path: `/news/`,
component: latestNewsPage,
context: {
// This time the entire product is passed down as context
product: edge
}
});
});
});
return Promise.all([blog, news]);
};
Edit 21 November:
I replaced the above code with my attempt to use a non-static query
I added the gatsby-node config
I give here a little explanation: the BlogPost.js and NewsSingle.js are templates that create new pages for each post or news post (from Netlify CMS)
The LatestNews.js is a component in a page. This is the main page of the news. Where are shown all the news? With a static query, it works fine, however, I need to make the "limit" a variable in order to apply a load more button, that onClick will increase the limit, thus showing more news posts on the loop.
With the above configuration it says:
warning The GraphQL query in the non-page component "/home/user/projectname/src/components/pages-implementation/news/sections/LatestNews.js" will not be run.
Exported queries are only executed for Page components. It's possible you're
trying to create pages in your gatsby-node.js and that's failing for some
reason.
useStaticQuery (hence the name) does not allow to receive variables. If you take a look at the docs:
useStaticQuery does not accept variables (hence the name “static”),
but can be used in any component, including pages
The only way to pass variables in a GraphQL query is by using the context API in the gatsby-node.js. For example:
queryResults.data.allProducts.nodes.forEach(node => {
createPage({
path: `/products/${node.id}`,
component: productTemplate,
context: {
// This time the entire product is passed down as context
product: node
}
});
});
};
In the snippet above, will be a product variable in the context with the whole node. It can be accessed through pageContext prop in the destination template or used as a query parameter.

How to get Gatsby Images based on results of PageQuery?

I'd like to do something like the following so I can get Gatsby Images dynamically:
const image = 'gastby-astronaut.png';
export const imageQuery = graphql`
{ allImageSharp (
filter: {
fluid: {
originalName: {
regex: "/${image}/"
}
}
}
){
edges {
node {
fluid {
originalName
}
}
}
}
}
`;
However, I can't figure out how to connect this query to an initial query that would get the 'gatsby-astronaut.png', or perform this query from a subcomponent with a . I get this error when I try this:
Error: BabelPluginRemoveGraphQL: String interpolations are not allowed
in graphql fragments. Included fragments should be referenced as
`...MyModule_foo`.
Any suggestions on the proper way to return Gatsby Images dynamically?
Ah, yeah Gatsby extracts GraphQL queries from your pages through static analysis: they load the file as text, parse it, and extract the query, all before the actual file gets executed. This means that your typical tagged-template literal functionality isn't there.
The only way to filter is through context provided when createPage is called from gatsby-node.js. I.e. if you do this:
exports.createPages = ({ graphql, actions }) =>
graphql(`some query here`).then(result => {
actions.createPage({
path: "/output-path/",
component: path.resolve(`./src/templates/your_template.jsx`),
context: { image: result.data.yourImage },
})
})
Then you can do this in your page query:
query SomePage($image: String!) {
allImageSharp (
filter: {
fluid: {
originalName: {
regex: $image
}
}
}
){
edges {
node {
fluid {
originalName
}
}
}
}
}
Here's a solution I came up with... pretty janky, but it works:
import PropTypes from 'prop-types';
import React from 'react';
import Img from 'gatsby-image';
import { useStaticQuery, graphql } from 'gatsby';
const Image = ({ imageYouWant }) => {
const data = useStaticQuery(
graphql`
query allTheImagesQuery{
allImageSharp {
edges {
node {
fluid(maxWidth:1000) {
...GatsbyImageSharpFluid
originalName
}
}
}
}
}`,
);
const TheImageYouWant = data.allImageSharp.edges
.filter(edge => edge.node.fluid.originalName === imageYouWant)
.map(myImage => <Img fluid={myImage.node.fluid} />);
return (
<>
{ TheImageYouWant }
</>
);
};
Image.propTypes = {
imageYouWant: PropTypes.string,
};
Image.defaultProps = {
imageYouWant: '',
};
export default Image;

Resources