I am trying to make an image slider with React and Gatsby but the images aren't rendering and I get this in console
static-image.server.tsx:51 No data found for image "undefined"
Could not find values for the following props at build time: src
This is my code. When I change the image source to '../images/1.png' instead of images[sliderIndex] the image will show.
const ImageSlider = (props) => {
const [sliderIndex, setSliderIndex] = useState(0);
const images = ['../images/1.png', '../images/2.png']
useEffect(() => {
const sliderLoop = setInterval(() => {
if(sliderIndex+1 > images.length-1) {
setSliderIndex(0)
} else {
setSliderIndex(sliderIndex+1)
}
}, 5000)
return () => clearInterval(sliderLoop)
}, [sliderIndex])
console.log(sliderIndex)
return (
<>
{props.children}
<StaticImage src={images[sliderIndex]} alt=""/>
</>
)
}
<StaticImage /> would only works with a static string, see this part of the docs for more info.
You'd have to use the <GatsbyImage /> component instead & query image data manually, as instructed here. I copied the relevant sample code below.
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
I'm trying to render rich text in my Gatsby v4 about page for my site, but I'm having trouble finding any info on how to render the data. I've read about adding blocks, but I'm lost on what I should be including or how to go about this. I really just need to render links, headers, and body text in the rich text. Could someone walk me through this?
Here's my component code snippet. The data is all coming through the query correctly in the page so far. I just want the text to go where it says "TEXT GOES HERE"
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import {renderRichText} from "gatsby-source-contentful/rich-text"
import {BLOCKS, MARKS} from "#contentful/rich-text-types"
import * as aboutStyles from "../styles/about.module.scss"
const query = graphql`
{
contentfulAbout {
about
bioImage {
title
url
gatsbyImageData(
layout: FULL_WIDTH
placeholder: BLURRED
resizingBehavior: SCALE
width: 1000
)
}
aboutText {
raw
}
}
}
`
const AboutSection = () => {
const data = useStaticQuery(query);
const contentfulAbout = data.contentfulAbout
return (
<div className={aboutStyles.parent}>
<section className={aboutStyles.container}>
<div className={aboutStyles.image}>
<GatsbyImage className={aboutStyles.bioImage} image={contentfulAbout.bioImage.gatsbyImageData} alt={contentfulAbout.bioImage.title} />
</div>
<div className={aboutStyles.text}>
<h2>{contentfulAbout.about}</h2>
<p>TEXT GOES HERE</p>
</div>
</section>
</div>
)
}
The idea is to use the exposed BLOCKS and MARKS to fully customize the fetched data from Contentful 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>,
},
},
}
renderRichText(node.bodyRichText, options)
Source: https://www.contentful.com/developers/docs/tutorials/general/rich-text-and-gatsby/
In that way, you can render a span with "bold" className when a MARKS.BOLD is fetched, using your own customized output.
In the snippet above, there's missing the implementation into a "standard" component, but the idea relies on the same fact. Using renderRichText what accepts two arguments:
The first one: your rich text node (aboutText in your case)
The second argument: the options with your custom output
Applied to your code, it should look like:
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
import * as aboutStyles from "../styles/about.module.scss"
const options = {
renderMark: {
[MARKS.BOLD]: text => <strong>{text}</strong>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <p>{children}</p>,
},
}
const query = graphql`
{
contentfulAbout {
about
bioImage {
title
url
gatsbyImageData(
layout: FULL_WIDTH
placeholder: BLURRED
resizingBehavior: SCALE
width: 1000
)
}
aboutText {
raw
}
}
}
`
const AboutSection = () => {
const data = useStaticQuery(query);
const contentfulAbout = data.contentfulAbout
return (
<div className={aboutStyles.parent}>
<section className={aboutStyles.container}>
<div className={aboutStyles.image}>
<GatsbyImage className={aboutStyles.bioImage} image={contentfulAbout.bioImage.gatsbyImageData} alt={contentfulAbout.bioImage.title} />
</div>
<div className={aboutStyles.text}>
<h2>{contentfulAbout.about}</h2>
<p>{renderRichText(contentfulAbout.aboutText, options)}</p>
</div>
</section>
</div>
)
}
Of course, it may need some tweaking. Note that I've simplified the output so customize it as you wish/need.
Other resources:
https://www.contentful.com/developers/docs/concepts/rich-text/
I have another question in the project I'm doing, using NextJS to create a site with a blog part in Wordpress, through WPGraphQL, and I need support in a specific part. Let's go...
I managed to pull the highlighted image with almost no problems, I broke my head a bit, but it worked. The result that's functioning is the post excerpt, the code and the query were as follows (in time: image merely to test, it was the first idea that came to my mind, the site is not about Pokemon):
Image with working image in the excerpt, codes below
NextJS code:
import { LastPosts, PostContainer } from "./Styled";
const RecentBlogPosts = ({lastPosts}) => {
const posts = lastPosts;
return (
<LastPosts>
<h1> ÚLTIMAS POSTAGENS </h1>
{posts?.map((post) => {
return (
<PostContainer key={post.slug}>
<img src={post.featuredImage?.node.sourceUrl} alt="" />
<Link href={`/post/${post.slug}`}>
<a>
<h3> { post.title } </h3>
<div dangerouslySetInnerHTML={{ __html: post.excerpt }} />
<button> Saiba mais </button>
</a>
</Link>
</PostContainer>
)
})}
</LastPosts>
)
};
export default RecentBlogPosts;
Query for this part:
export const RECENT_POSTS = `query RecentPosts {
posts(where: {orderby: {field: DATE, order: DESC}}, first: 2) {
nodes {
id
slug
title
excerpt
featuredImage {
node {
sourceUrl
}
}
}
}
}`;
But I tried to pull the same image in the full blogpsot and it wasn't working... It appears when I view the post from the generic WordPress admin template, but not at my NextJS site, which i See through localhost:3000/post/[here would be the post title in slug] that I'm using. The rest is normal, text with all fonts and specifications with styled components, as well as tags, they work without any problem. The following is the same schema: image with result, code and query that I am using, this time for the part where I'm having problems:
Image with blogpost not working, codes below
NextJS code:
import fetcher from "../../lib/fetcher";
import { GET_ALL_POSTS_WITH_SLUG, POST_BY_SLUG } from "../../lib/wordpress/api";
import { useRouter } from "next/router";
import { Reset } from "../../constants/StyledConstants";
import Header from "../../components/Header/Header";
import { BlogArticle, BlogPostContent, TagLinks, TagWrapper } from "./StyledPost";
import Footer from "../../components/Footer/Footer";
const post = ({ postData }) => {
const blogPost = postData.data.post;
console.log(postData);
const tags = postData.data.post.tags.nodes;
const router = useRouter;
if(!router.isFallback && !blogPost?.slug) {
return <div>erro</div>
}
return (
<>
<Reset />
<Header />
<BlogPostContent>
{router.isFallback ? (
<div> Carregando...... </div>
) : (
<div>
<h1> { blogPost.title } </h1>
<img src={post.featuredImage?.node.sourceUrl} alt="imagem não aparece" />
<BlogArticle dangerouslySetInnerHTML={{ __html: blogPost.content }} />
<TagWrapper>
{tags.map((tag) => <TagLinks href={`/tags/${tag.slug}`} key={tag.slug}> { tag.name } </TagLinks>)}
</TagWrapper>
</div>
)}
</BlogPostContent>
<Footer />
</>
)
}
export default post;
export async function getStaticPaths() {
const response = await fetcher(GET_ALL_POSTS_WITH_SLUG);
const allposts = await response.data.posts.nodes;
return {
paths: allposts.map((post) => `/post/${post.slug}`) || [],
fallback: false
};
}
export async function getStaticProps({ params }) {
const variables = {
id: params.slug,
idType: "SLUG"
};
const data = await fetcher(POST_BY_SLUG, { variables })
return {
props: {
postData: data
},
};
}
Query being used:
export const POST_BY_SLUG = `query PostBySlug($id: ID!, $idType: PostIdType!) {
post(id: $id, idType: $idType) {
title
slug
date
content
featuredImage {
node {
sourceUrl
}
}
tags {
nodes {
name
slug
}
}
}
}`;
I tried to use {post.featuredImage?.node.sourceUrl} because, as far as I understand, following the same basis I did for the excerpt in the blogspot, it should work, but I guess I was wrong... I tried to think of other ways to do it to get to the image, without success... Could someone help to point out where I am wrong please? Thank you very much in advance!!
I'd need a bit of advice with gatsby-image. I'm building a gallery with a custom lightbox.
Query:
export const getAllPhotos = graphql`
query GetAllPhotos {
allStrapiPortfolio {
photos: nodes {
categories {
name
}
photo {
childImageSharp {
fluid(quality: 100) {
...GatsbyImageSharpFluid
}
}
}
strapiId
}
}
}
`;
I'm displaying my images in a grid. photos prop contains images from the query above.
Gallery.js
const Gallery = ({ photos }) => {
const [currentPhotoId, setCurrentPhotoId] = useState(null);
const handleClick = (e) => {
const lightbox = document.getElementById("lightbox");
const div = parseInt(e.target.parentElement.parentElement.className.split(" ")[0]);
const imgSelected = e.target.parentElement.parentElement.className.includes("gatsby-image-wrapper");
if (imgSelected) {
setCurrentPhotoId(div);
lightbox.classList.add("lightbox-active");
} else {
setCurrentPhotoId(null);
lightbox.classList.remove("lightbox-active");
}
};
return (
<main className="portfolio-gallery" onClick={(e) => handleClick(e)}>
photos.map((item) => {
return (
<Image
key={item.strapiId}
fluid={item.photo.childImageSharp.fluid}
data-categories={item.categories[0]}
alt={item.categories[0].name}
className={`${item.strapiId}`}
/>
);
})}
<Lightbox photos={photos} currentPhotoId={currentPhotoId} />
</main>
);
};
export default Gallery;
And after an image is clicked I display my lightbox component
lightbox.js
const Lightbox = ({ photos, currentPhotoId }) => {
const currentPhoto = photos.filter((photo) => photo.strapiId === currentPhotoId)[0];
return currentPhotoId === null ? (
<div id="lightbox" className="lightbox">
<h4>Nothing to display, this is hidden currently</h4>
</div>
) : (
<div id="lightbox" className="lightbox">
<Image fluid={currentPhoto.photo.childImageSharp.fluid} />
</div>
);
};
export default Lightbox;
Problem is that when I display my image in the lightbox stretched across the screen it is in a bad quality as the query downloads images in small sizes. However, my original images are 5000px wide. Unfortunately, as gatsby page queries are generated at the build I'm stuck with this quality.
Any idea about a workaround?
The idea when dealing with multiple images with gatsby-images for your kind of use-case is to query different image resolutions using the art director workaround (breakpoints). The idea, based on the documentation is to:
import React from "react"
import { graphql } from "gatsby"
import Img from "gatsby-image"
export default ({ data }) => {
// Set up the array of image data and `media` keys.
// You can have as many entries as you'd like.
const sources = [
data.mobileImage.childImageSharp.fluid,
{
...data.desktopImage.childImageSharp.fluid,
media: `(min-width: 768px)`,
},
]
return (
<div>
<h1>Hello art-directed gatsby-image</h1>
<Img fluid={sources} />
</div>
)
}
export const query = graphql`
query {
mobileImage: file(relativePath: { eq: "blog/avatars/kyle-mathews.jpeg" }) {
childImageSharp {
fluid(maxWidth: 1000, quality: 100) {
...GatsbyImageSharpFluid
}
}
}
desktopImage: file(
relativePath: { eq: "blog/avatars/kyle-mathews-desktop.jpeg" }
) {
childImageSharp {
fluid(maxWidth: 2000, quality: 100) {
...GatsbyImageSharpFluid
}
}
}
}
`
I am having an error "TypeError: Cannot read property 'map' of undefined" when I tried to use documentToReactComponents to output the body of contentful post. If I comment it out, and try to console log, it displays documents etc.
Having edited the code to the suggested answer, I am receiving another error "Node is not defined" using renderRichText
import React from 'react'
import Layout from '../components/layout'
import { graphql } from 'gatsby'
import Head from '../components/head'
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
export const query = graphql`
query($slug: String!) {
contentfulBlogPost(
slug: {
eq: $slug
}
) {
title
slug
publishedDate(formatString: "MMM Do, YYYY")
body {
raw
}
}
}
`
const Blog = (props) => {
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 => {
const alt= node.data.target.fields.title['en-US']
const url= node.data.target.fields.file['en-US'].url
return (
<>
<img src={url} alt={alt} />
</>
)
},
[BLOCKS.EMBEDDED_ENTRY]: node => {
return (
<Layout>
<Head home_title="Blog Post" />
<h1>{props.data.contentfulBlogPost.title}</h1>
<p>{props.data.contentfulBlogPost.publishedDate}</p>
{props.data.contentfulBlogPost.body}
</Layout>
)
},
},
}
renderRichText(node.bodyRichText, options)
}
export default Blog
I have tried to read through from Github but still receiving the same error. I need help.
All you need to do is to add references to the graphql query, unlike with json, the body has raw and references. With that being said, you need to append this to the body after the raw;
references{
contentful_id
title
fixed{
src
}
}
Then you can you id to search for the title and src as you can see above.
import React from 'react'
import Layout from '../components/layout'
import { graphql } from 'gatsby'
import Head from '../components/head'
import { documentToReactComponents } from '#contentful/rich-text-react-renderer'
import { BLOCKS } from "#contentful/rich-text-types"
export const query = graphql`
query($slug: String!){
post: contentfulBlogPost(slug:{eq:$slug}){
title
publishedDate(formatString: "MMMM Do, YYYY")
body{
raw
references{
contentful_id
title
fixed{
src
}
}
}
}
}
`
const Blog = (props) => {
const assets = new Map(props.data.post.body.references.map(ref => [ref.contentful_id,ref]))
const options = {
renderNode:{
[BLOCKS.EMBEDDED_ASSET]: node => {
const url = assets.get(node.data.target.sys.id).fixed.src
const alt = assets.get(node.data.target.sys.id).title
return <img alt={alt} src={url}/>
}
}
}
return(
<Layout>
<h1>{props.data.post.title}</h1>
<p>{props.data.post.publishedDate}</p>
{documentToReactComponents(JSON.parse(props.data.post.body.raw),options)}
</Layout>
)
}
export default Blog
I would suggest using the following approach:
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)
Note: extracted from Contentful docs
Using renderRichText built-in method (from gatsby-source-contentful/rich-text). Your code seems a little bit deprecated or at least, some methods.
The following code will never work:
"embedded-asset-block" : (node) => {
const alt= node.data.target.fields.title['en-US']
const url= node.data.target.fields.file['en-US'].url
return <imag src={url} alt={alt} />
}
You need to use the providers of the dependency (#contentful/rich-text-types), that are BLOCKS and MARKS. You can always access to the dependency to check the methods, however, embedded-asset-block stands for BLOCKS.EMBEDDED_ASSET or BLOCKS.EMBEDDED_ENTRY, so:
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 => {
// manipulate here your embedded asset
return (
<>
<h2>Embedded Asset</h2>
<pre>
<code>{JSON.stringify(node, null, 2)}</code>
</pre>
</>
)
},
[BLOCKS.EMBEDDED_ENTRY]: node => {
// manipulate here your embedded entry
return (
<>
<div>I'm an embedded entry</div>
</>
)
},
},
}
renderRichText(node.bodyRichText, options)
We are using react-select and fetching the items as the user types. I am not able to make it work with react-apollo.
Can someone help me provide a guideline?
Here is my unsuccessful attempt:
class PatientSearchByPhone extends Component {
updateProp = mobile => {
if (mobile.length < 10) return;
this.props.data.refetch({ input: { mobile } });
};
render() {
console.log(this.props.data);
return <AsyncSelect cacheOptions loadOptions={this.updateProp} />;
}
}
const FETCH_PATIENT = gql`
query Patient($input: PatientSearchInput) {
getPatients(input: $input) {
id
first_name
}
}
`;
export default graphql(FETCH_PATIENT, {
options: ({ mobile }) => ({ variables: { input: { mobile } } })
})(PatientSearchByPhone);
Versions:
"react-apollo": "^2.1.11",
"react-select": "^2.1.0"
Thanks for your time.
I got an e-mail asking a response to this question. It reminds me of this XKCD comics:
I do not recall the exact solution I implemented, so I setup a complete example for this.
This app (code snippet below) kickstarts searching as soon as you type 4 characters or more in the input box (You are expected to type artist's name. Try vinci?). Here is the code:
import React, { useState } from "react";
import "./App.css";
import AsyncSelect from "react-select/async";
import ApolloClient, { gql } from "apollo-boost";
const client = new ApolloClient({
uri: "https://metaphysics-production.artsy.net"
});
const fetchArtists = async (input: string, cb: any) => {
if (input && input.trim().length < 4) {
return [];
}
const res = await client.query({
query: gql`
query {
match_artist(term: "${input}") {
name
imageUrl
}
}
`
});
if (res.data && res.data.match_artist) {
return res.data.match_artist.map(
(a: { name: string; imageUrl: string }) => ({
label: a.name,
value: a.imageUrl
})
);
}
return [];
};
const App: React.FC = () => {
const [artist, setArtist] = useState({
label: "No Name",
value: "https://dummyimage.com/200x200/000/fff&text=No+Artist"
});
return (
<div className="App">
<header className="App-header">
<h4>Search artists and their image (type 4 char or more)</h4>
<AsyncSelect
loadOptions={fetchArtists}
onChange={(opt: any) => setArtist(opt)}
placeholder="Search an Artist"
className="select"
/>
<div>
<img alt={artist.label} src={artist.value} className="aimage" />
</div>
</header>
</div>
);
};
export default App;
You can clone https://github.com/naishe/react-select-apollo it is a working example. I have deployed the app here: https://apollo-select.naishe.in/, may be play a little?
The other option is to execute the graphql query manually using the client that is exposed by wrapping the base component with withApollo.
In the example below, we have,
BaseComponnent which renders the AsyncSelect react-select component
loadOptionsIndexes which executes the async graphql fetch via the client
BaseComponent.propTypes describes the required client prop
withApollo wraps the base component to give us the actual component we'll use elsewhere in the react app.
const BaseComponent = (props) => {
const loadOptionsIndexes = (inputValue) => {
let graphqlQueryExpression = {
query: QUERY_INDEXES,
variables: {
name: inputValue
}
}
const transformDataIntoValueLabel = (data) => {
return data.indexes.indexes.map(ix => { return { value: ix.id, label: ix.name }})
}
return new Promise(resolve => {
props.client.query(graphqlQueryExpression).then(response => {
resolve(transformDataIntoValueLabel(response.data))
})
});
}
return (
<>
<div className="chart-buttons-default">
<div className="select-index-input" style={{width: 400, display: "inline-block"}}>
<AsyncSelect
isMulti={true}
cacheOptions={true}
defaultOptions={true}
loadOptions={loadOptionsIndexes} />
</div>
</div>
</>
)
}
BaseComponent.propTypes = {
client: PropTypes.any,
}
const ComplementComponent = withApollo(BaseComponent);
Sorry if the example is a little off - copy and pasted what I had working rather than moving on without giving back.