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!
Related
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.
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>
})}
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.
I have been working with Gatsby and GraphQL on a project for a bit now. This is my first time using this platform so still figuring out the right way to go about it.
Right now, I have an index.js which loads a list of users from a GraphQL query such as
allUserList {
users {
name
imageUrl
}
}
}
imageUrl is expected to send the correct file path i.e images/image.png
This is sent directly to a react component which is used to display the user info
const Users = {data} => {
return (
data.users.map(user => {
return (
<User name={user.name} imageUrl={user.imageUrl}/>
)
}
)
}
What I am trying to figure out now is, imageUrl contains the relative url to the image in the codebase. I would be required to run a image query on this url to get the image object which I can then pass to gatsby image. Something like this
{
image: file(relativePath: { eq: 'image.png' }) {
childImageSharp {
fixed(width: 160, height: 160) {
...GatsbyImageSharpFixed
}
}
}
}
However as I understand it isnt possible to parameterize the image url here. What is the best way to go about this? Is this even the correct approach to show a list with images in this manner?
However as I understand it isnt possible to parameterize the image url here.
Correct. Gatsby runs GraphQL queries ONCE at build time and then never again until you gatsby develop or gatsby build again.
Two approaches:
First approach: You name your images after user.name. You can then query all your images and filter for each user by file name using the graphQL originalName attribute:
const UserSupplier = () => {
const { allFile } = useStaticQuery(graphql`
query {
allFile(filter: {
extension: {regex: "/(jpg)|(jpeg)|(png)/"},
sourceInstanceName: {eq: "user"}})
{
edges {
node {
childImageSharp {
fluid(maxWidth: 150, quality: 100) {
originalName
...GatsbyImageSharpFluid
}
}
}
}
}
}`);
const user = users.filter(el => el.userId === userId)[0];
const { fluid } = user .node.childImageSharp;
return (
<UserComponent>
<GatsbyImage fluid={fluid} alt={fluid.originalName} />
</UserComponent>
);
};
Second approach: Query for your childImageSharp image object in your allUserList query. You can pass the image as props to the component that renders the image. That would make your query result query quite data intensive depending on how many users there are.
allUserList {
users {
name
imageUrl
childImageSharp { // the query path is probably wrong since I don't know your project
fixed(width: 160, height: 160) {
...GatsbyImageSharpFixed
}
}
}
}
}
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;