Error when trying to render data conditionally in React - reactjs

I am querying data from a CMS via Graphql into my Gatsby project.
Sadly I was not thinking ahead and the content types text field wasn't set to required or given a default value, so now the value of this text field is for some elements null.
I tried avoiding this giving me issues by rendering the elements through a conditional statement checking if the element is empty or not.
This is my entire file:
import React from 'react'
import { graphql, Link } from 'gatsby'
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import Layout from '../components/Layout'
import { MDXProvider } from "#mdx-js/react"
import MDXRenderer from "gatsby-plugin-mdx/mdx-renderer"
export default function MalreiTemplate({ data }) {
const picture = data.allContentfulMalerei.nodes
return (
<Layout>
{picture.map(pic => {
const image = getImage(pic.image)
return(
<div key={pic.slug} className="mb-3">
<div className="pt-3 d-flex justify-content-between">
<span><Link to="/malerei" style={{textDecoration: "none", color: "#589AAD"}}>← zurück</Link></span>
</div>
<hr />
<div className="artwork-detail-container d-flex">
<div className="artwork-detail-image-container">
<GatsbyImage image={image}/>
</div>
<div className="artwork-detail-text">
<h3>{pic.title}, {pic.year}</h3>
<h4>{pic.type}</h4>
<p className="pt-4">
<MDXProvider>
<MDXRenderer>
{pic.description !== null ? <p>{pic.description}</p> : <p>.</p>}
</MDXRenderer>
</MDXProvider>
</p>
</div>
</div>
<hr/>
</div>
)})}
</Layout>
)
}
export const query = graphql`
query($slug: String!) {
allContentfulMalerei(filter: { slug: { eq: $slug } }) {
nodes {
year
width
title
description {
childMdx {
body
}
}
image {
gatsbyImageData(width: 600, placeholder: BLURRED)
}
}
}
}
`
This is the relevant part:
<MDXProvider>
<MDXRenderer>
{pic.description ? pic.description.childMdx.body : <p>.</p>}
</MDXRenderer>
</MDXProvider>
I am getting this error message in return when I visit pages for those elements that have no text field. On all other sites I can see the content with no error.
Would someone have an idea on how to fix this? Thanks in advance.

Personally I would replace:
<p className="pt-4">
<MDXProvider>
<MDXRenderer>
{pic.description ? <p>{pic.description.childMdx.body}</p> : <p>.</p>}
</MDXRenderer>
</MDXProvider>
</p>
with:
{ picDescription ? (<p className="pt-4">{pic.description}</p>) : null }
because I don't see what value the MDX components are adding there and I suspect that error is coming from MDX (but I don't know for sure).

Related

Working with images local files in Gatsby.js

I am trying to render a headshot for each artist displayed on the page. The artist data comes from a Json file, and the images are located in images/artists/headshots. I am using the regular img tag, but for some reason nothing is displaying on the page. Any help any one can give would be greatly appreciated.
import React from 'react'
import PropTypes from 'prop-types'
import { StaticImage } from 'gatsby-plugin-image'
import { useStyles } from './styles'
const ArtistsPage = ({ data }) => {
const classes = useStyles()
return (
<section>
<article className={classes.artistsBackground}>
<div className={classes.heroLayoutContainer}>
<h3 className={classes.title}>MEET THE ARTISTS</h3>
<StaticImage
className={classes.heroImage}
src='../../images/artists/hero-images/hero-image-artists.jpg'
alt='hero-image-artists'
placeholder='blurred'
/>
</div>
</article>
<article className={classes.artistsContainer}>
<div className={classes.flexContainer}>
{data.allArtistsJson.edges
.map(({ node }, idx) => {
const artist = `${node.firstName}+${node.lastName}`
.split('+')
.join(' ')
return (
<div className={classes.flexItem} key={idx}>
<div>
<img
src={`../../images/artists/headshots/${artist} Headshot.jpg`}
alt='artist-headshot'
/>
</div>
<div className={classes.artistCardName}>
{`${node.firstName} ${node.lastName}`.toUpperCase()}
</div>
<div className={classes.artistCardText}>{node.city}</div>
<div className={classes.artistCardText}>
{node.currentTeam}
</div>
</div>
)
})}
</div>
</article>
</section>
)
}
export default ArtistsPage
My image files are set up as:
FirstName LastName Headshots.jpg
I think your issue may comes from the white spaces in the naming. Your code looks good at first sight so try renaming your images with underscore or in camelCase:
<img
src={`../../images/artists/headshots/${artist}_Headshot.jpg`}
alt='artist-headshot'
/>
After much research and the nice people on Gatsby discord I found the answer to be… in a scenario like this I needed to add require().default.
Ex:
<img
src={require(../../images/artists/headshots/${artist} Headshot.jpg).default}
alt='artist-headshot'
/>

Caching in Gatsby JS

I have this Gatsby app where when I visit home page everything loads at first including the Testimonials section. However, when I visit another page, for instance the Blog list page and then click on the link back to homepage the data on Testimonials component won't load and you will see a blank area of the app where the Testimonials section is placed. In order to see the Testimonials list, you will have the refresh the page again.
For the record the data on my Testimonials component are being pulled out on Strapi JS that is deployed on Heroku.
So far I got this on my index.js:
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/Layout"
import Header from "../components/Header"
import Testimonials from '../components/Testimonials'
import Blogs from '../components/Blogs'
import SEO from '../components/SEO'
export default function Home({ data }) {
const {
allStrapiBlogs: { nodes:blogs },
} = data
return (
<>
<SEO />
<div className="main-container">
<Layout>
<Header />
<Testimonials title="Testimonials" />
<Blogs title="Latest Blogs" blogs={blogs} showAllBlogLinks/>
<Map />
</Layout>
</div>
</>
)
}
export const query = graphql`
{
allStrapiBlogs(filter: {featured: {eq: true}}, sort: {fields: published_date, order: DESC}, limit: 6) {
nodes {
id
title
content
slug
published_date(formatString: "MMMM DD, YYYY")
featured_image {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`
And then on my Testimonials.js component:
import React from "react"
import { graphql, useStaticQuery } from "gatsby"
import Image from "gatsby-image"
import { FaStar } from "react-icons/fa"
import Title from './Title'
const query = graphql`
{
allStrapiTestimonials {
nodes {
id
name
cite
text
photo {
childImageSharp {
fluid{
...GatsbyImageSharpFluid_withWebp
}
}
}
}
}
}
`
const Testimonials = ({ title }) => {
const data = useStaticQuery(query)
const { allStrapiTestimonials: { nodes:testimonials } } = data
return (
<div className="testimonial-section section-padding" id="testimonial" data-aos="zoom-in">
<div className="container">
<div className="row">
<div className="col-lg-12">
<div className="section-title-two center">
<Title title={title} />
</div>
</div>
</div>
<div className="testimonial-wrap row">
<div className="testimonial-slider owl-carousel">
{ testimonials.map(item => {
const { id, name, cite, text, photo } = item
return(
<div className="col-xl-8 col-lg-10 col-12 ml-auto mr-auto" key={id}>
<div className="testimonial-item mt-40">
<div className="testimonial_img">
<Image fluid={photo.childImageSharp.fluid} alt={name} style={{ position: "absolute", overflow: "visible", display: "block", width: "211px", height: "207px" }} />
</div>
<div className="testimonial_content xs-mt-40">
<div className="testimonial_content_item mb-30">
<div className="testimonial_content__pro">
<h4 className="mb-10">{ name }</h4>
<p>{ cite }</p>
</div>
<ul className="d-none d-sm-inline-block">
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
</ul>
</div>
<div>
<p>{ text } </p>
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
</div>
)
}
export default Testimonials
Any idea what's causing this error? How can I fix it?
I've faced a similar issue a few months ago and the fix depends strictly on the implementation and your code. Basically, what is happening is that React's doesn't understand that he needs to rehydrate some components because pointing to some global objects as window or document of the DOM, outside the React's ecosystem (without using states) may block that rehydration, in your cause, because of jQuery.
All the possible solutions that bypass this issue will be patches (like trying to add the cache). The ideal solution would avoid using jQuery, which points directly to the DOM, with React, which manipulates the virtual DOM (vDOM).
There's nothing wrong with the code you've shared, however, based on some other questions that you did, you are using jQuery and using the window object, which prevents the rehydration of some React components. You should get rid of jQuery or using some React-based approach. Something like this should do the trick to force a loader across the whole site:
const Layout = ({ children }) => {
const [loader, setLoader]=useState(true);
useEffect(()=>{
setTimeout(()=> {
setLoader(false)
}, 400)
}, [])
return <section className={`site__wrapper`}>
<Header />
<main>{loader ? <Preloader/> : children}</main>
</section>;
};

looping json data in Class Component

This is my index.js where I try to refer SampleApp
import React, { Component } from "react";
import { render } from "react-dom";
import './index.css';
import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect
} from "react-router-dom";
import SampleApp from "./pages/SampleApp";
import 'bootstrap/dist/css/bootstrap.min.css';
class App extends Component {
constructor() {
super();
this.state = {
name: "React",
isUserAuthenticated: true
};
}
render() {
return (
<Router>
<Switch>
<Route
exact
path="/"
render={() => {
return (
<Redirect to="/SampleApp" />
)
}}
/>
<Route exact path="/SampleApp" component={SampleApp} />
</Switch>
</Router>
);
}
}
render(<App />, document.getElementById("root"));
This is my SampleApp.js file. here I'm importing the Cards component from Cards.jsx
import React from 'react';
import '../../src/App.css';
import Cards from '../cards/cards';
const SampleApp = props => (
<React.Fragment>
<div className="App">
<div>
<div className="header">
<div className="header_fonts">
Sample Application
</div>
</div>
<div>
<div className="content_header_fonts">
This is sample app
</div>
<div className="content_fonts">
Sample app to deomntrate ideas.
</div>
<Cards></Cards>
</div>
</div>
</div>
</React.Fragment>
)
export default SampleApp;
this is my Cards.jsx file.
here I'm importing Card component and json data
import React, { Component } from "react";
import Card from './cardUI';
import CardData from '../source/data.json';
class Cards extends Component {
render() {
return
(
<div className="container-fluid d-flex justify-content-center">
<div className="row">
{
CardData.map((
{title, desc, icon, intro,developer_guide,api_ref }, id) =>
(
<div className="col-md-4">
<Card
title={title}
desc={desc}
intro={intro}
developer_guide={developer_guide}
api_ref={api_ref}/>
</div>
))
}
</div>
</div>
);
}
}
export default Cards;
this is a sample of my JSON file
[
{
"id" : 7,
"title" : "Melon Munchee",
"icon" : "https://cdn.onlinewebfonts.com/svg/img_393496.png",
"desc" : "If you are an Avatar fan, then this api is for you. Here you can find everything from Episodes to Characters.",
"intro": "intro_7",
"developer_guide": "d_link7",
"api_ref": "api_link7"
},
{
"id" : 8,
"title" : "Browns Barns",
"icon" : "https://cdn.onlinewebfonts.com/svg/img_386567.png",
"desc" : "Baseball fans? Computer nerds? Now, in one place, you have baseball data and an api to access it. Have fun!.",
"intro": "intro_8",
"developer_guide": "d_link8",
"api_ref": "api_link8"
}
]
Card.jsx file
This is how implemented the Card component
import React from 'react';
import "../../node_modules/bootstrap/dist/css/bootstrap.min.css";
import * as Icon from '../../node_modules/react-bootstrap-icons';
import './card-style.css';
const Card = props =>{
return(
<div className="card text-center">
<div className="card-body text-dark">
<Icon.Alarm></Icon.Alarm>
<h4 className="card-title">
{props.title}
</h4>
<p className="card-text text-secondary">
{props.desc}
</p>
<ul class="list-group">
<li class="list-group-item">{props.intro}</li>
<li class="list-group-item">{props.developer_guide}</li>
<li class="list-group-item">{props.api_ref}</li>
</ul>
</div>
</div>
)
}
export default Card;
but I'm getting an error as following
Error: Cards(...): Nothing was returned from render. This usually
means a return statement is missing. Or, to render nothing, return null.
19 stack frames were collapsed. Module.
src/index.js:44 41 | } 42 | } 43 |
44 | render(, document.getElementById("root"));
I am actually going to take a guess here and say that your specific error is caused by the new line after your return statement. So remove it to make it look like this return ( and it should work... or at least that error should go away.
Check out this sandbox: https://codesandbox.io/s/xenodochial-fog-y8pk2?file=/src/App.js
just go ahead and add a new line after the return and see your exact error.
It's just a typo mistake. When you use line terminator next to the return statement, JS adds semicolon automatically and that will be the end of function execution and returns undefined. That's why your Cards component is not able to find the JSX because Cards render returns undefined.
As per MDN docs.
The return statement is affected by automatic semicolon insertion (ASI). No line terminator is allowed between the return keyword and the expression.
To fix this, update Cards render function with this
class Cards extends Component {
render() {
return ( // was the issue earlier
<div className="container-fluid d-flex justify-content-center">
<div className="row">
{
CardData.map(({ title, desc, icon, intro,developer_guide,api_ref }, index) => (
<div className="col-md-4" key={title + index}>
<Card
title={title}
desc={desc}
intro={intro}
developer_guide={developer_guide}
api_ref={api_ref} />
</div>
)
)
}
</div>
</div>
);
}
}

Filter graphql data from frontend using react hooks

I have the following React page in Gatsby repo which runs a graphql page query to Netlify CMS to fetch the data :
import styled from 'styled-components';
import Layout from '../../components/Layout';
import Dropdown from '../../components/Dropdown';
import CardBlock from '../../components/creditcards/CardBlock';
import { graphql } from 'gatsby';
const CCListing = (response) => {
console.log("Credit Cards Listings")
console.log(response);
const {creditCards} = response.data;
return (
<CCListingContainer>
<Layout>
<div className="container">
<h1 className="section-title">Recommended cards for you</h1>
<h4 className="section-subtitle">Based on your answers, we’ve provided the top matches for you to compare below. Review and select the one that best matches your needs.</h4>
<div className="filters-container">
<Dropdown default="Travel" />
<Dropdown default="No Annul Fee" />
<Dropdown default="Welcome Bonus" />
<Dropdown default="Low to High" />
</div>
<div className="cards-container">
{creditCards.edges.map(item => (
<CardBlock cardData={item} />
))}
</div>
</div>
</Layout>
</CCListingContainer>
)
}
const CCListingContainer = styled.div`
.filters-container {
margin-top: 50px;
display: flex;
}
.cards-container {
margin-top: 2rem;
}
`
export const pageQuery = graphql`
query CreditCardListing {
creditCards:allMarkdownRemark(filter: {frontmatter:{ templateKey: { eq: "credit-card-post" } }}) {
edges {
node {
frontmatter {
title
cardImage {
childImageSharp {
fixed(width: 299, height: 189) {
...GatsbyImageSharpFixed
}
}
}
fee
purchaseInterest
cashAdvanceInterest
href
summaryDescription
}
}
}
}
}
`
export default CCListing;
On this page, I have these dropdowns at the top for filtering data coming from query :
How can I filter data based on dropdown selections ? Not sure how to introduce variables or react hooks in graphql query to make it dynamic.
It's a broad question and without having a CodeSandbox or something similar it's difficult to figure out how the code will behave. However, what you need to do, is to add the GraphQL query result to the state and then play with the state:
import styled from 'styled-components';
import Layout from '../../components/Layout';
import Dropdown from '../../components/Dropdown';
import CardBlock from '../../components/creditcards/CardBlock';
import { graphql } from 'gatsby';
const CCListing = (response) => {
console.log("Credit Cards Listings")
console.log(response);
const {creditCards} = response.data;
const [yourData, setYourData]= useState({}); // initially set to empty to avoid re-hydration issues or asynchronous execution issues if the data is empty
useEffect(() => {
setYourData(creditCards)
},[])
yourHandleFunction= ()=>{
// your stuff here. You may need to clone your state object and manipulate the copy, depending on your use-case.
}
return (
<CCListingContainer>
<Layout>
<div className="container">
<h1 className="section-title">Recommended cards for you</h1>
<h4 className="section-subtitle">Based on your answers, we’ve provided the top matches for you to compare below. Review and select the one that best matches your needs.</h4>
<div className="filters-container">
<Dropdown onClick={yourHandleFunction} default="Travel" />
<Dropdown onClick={yourHandleFunction} default="No Annul Fee" />
<Dropdown onClick={yourHandleFunction} default="Welcome Bonus" />
<Dropdown onClick={yourHandleFunction} default="Low to High" />
</div>
<div className="cards-container">
{creditCards.edges.map(item => (
<CardBlock cardData={item} />
))}
</div>
</div>
</Layout>
</CCListingContainer>
)
}
const CCListingContainer = styled.div`
.filters-container {
margin-top: 50px;
display: flex;
}
.cards-container {
margin-top: 2rem;
}
`
export const pageQuery = graphql`
query CreditCardListing {
creditCards:allMarkdownRemark(filter: {frontmatter:{ templateKey: { eq: "credit-card-post" } }}) {
edges {
node {
frontmatter {
title
cardImage {
childImageSharp {
fixed(width: 299, height: 189) {
...GatsbyImageSharpFixed
}
}
}
fee
purchaseInterest
cashAdvanceInterest
href
summaryDescription
}
}
}
}
}
`
export default CCListing;
It's quite self-explanatory. You need to set your state with useState (initially as empty to prevent any type of asynchronous execution that may break the code) and, once the DOM tree is loaded (useEffect with empty deps, []), your hook will fill your state with your GraphQL data with setYourData(creditCards).
The next step is to attach an onClick function event on your dropdowns and do your stuff there. Keep in mind that, depending on your needs and your use-case, you may need to copy the state object to avoid issues with your data. Something like this should work:
const clonedData = [...yourData]; // use {} if it's an object

How do you write a conditional to ignore a null value for an image in gatsby?

Strapi has an option to make an image required or not, but regardless of whether you select the required button, it makes it required and renders a null value in gatsby. How can I programmatically tell gatsby to render just the rich text if that is selected and ignore the null image value if not picture is uploaded? I wanted to do something like {image && ()}, but not sure how to map that..
import React from "react"
import { graphql } from "gatsby"
import ReactMarkdown from "react-markdown"
import Image from "gatsby-image"
//import Layout from "../components/layout"
const ComponentName = ({ data }) => {
const { title, layout } = data.strapiBasics;
return (
<div>
<h1>{title}</h1>
{layout.map(item => {
return (
<span key={item.id}><ReactMarkdown source={item.rich_text} />
<Image fluid={item.image.childImageSharp.fluid} />
</span>
)
})}
</div>
)
}
export const query = graphql`
query BasicQuery($slug: String!) {
strapiBasics(Slug: {eq: $slug}) {
title: Title
slug: Slug
layout: Layout {
id
rich_text
image: Image {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`
export default ComponentName
Something like this should do the trick:
return (
<div>
<h1>{title}</h1>
{layout.map(item => {
return (
<span key={item.id}><ReactMarkdown source={item.rich_text} />
{item.image && <Image fluid={item.image.childImageSharp.fluid} />}
</span>
)
})}
</div>
);
Note: you may need to change the item.Image to check if it meets the condition.
Since you are aliasing your GraphQL query:
image: Image {...}
Your object will Image, not image, and so on with the rest.
You don't need to add the && logical operator inside, you just can return an image if it exists or return a markdown if don't.

Resources