How to handle "Cannot read property 'node' of null" in a query - reactjs

I am pretty new to React/Gatsby and am doing a query with Apollo.
The thing is that if there is no title or image it'll say "Cannot read property 'node' of null". I get that because if I do not set a title or image in my headless CMS there's no data to read.
How can I make it conditional so that if 'title' is empty don't render it. Any other suggestions or tips about my code are always welcome!
Here's an example of my code
import React from "react"
import Container from "react-bootstrap/Container"
import Image from "react-bootstrap/Image"
import { useQuery, gql } from "#apollo/client"
const APOLLO_QUERY = gql`
{
posts {
nodes {
title
databaseId
content(format: RENDERED)
featuredImage {
node {
sourceUrl
}
}
}
}
}
`
const ApolloTest = () => {
const { data } = useQuery(APOLLO_QUERY)
return (
<Container>
{data &&
data.posts.nodes.map(post => {
return (
<article key={post.databaseId}>
<h3>{post.title}</h3>
<p>{post.content}</p>
<Image
src={post.featuredImage.node.sourceUrl}
alt={post.title}
style={{ width: "150px" }}
fluid
></Image>
</article>
)
})}
</Container>
)
}
export default ApolloTest

I would recommend short-circuit evaluation to first check if the information exists prior to trying to render it. A quick example is {post.title && <h3>{post.title}</h3>}, which will only render the h3 and everything inside it if post.title is truthy. You can extend this to work for the image as well:
return (
<Container>
{data?.posts.nodes.map(post => {
return (
<article key={post.databaseId}>
{post.title && <h3>{post.title}</h3>}
<p>{post.content}</p>
{post.featuredImage && <Image
src={post.featuredImage.node.sourceUrl}
alt={post.title}
style={{ width: "150px" }}
fluid
/>}
</article>
)
})}
</Container>
)

Related

NextJS: some props lost in child component

I created a project using NextJS template Blog starter kit, found here: https://vercel.com/templates/next.js/blog-starter-kit
I've successfully fetched my own posts from a graphql-endpoint and can render them, but when I add values to the post object on my own that weren't included in the original NextJS-project and pass them down to child components, they are available at first but then go undefined and throw an error if I try to use those values.
Please see below code and screenshot of how the values goes from defined to undefined.
I'm new to both Typescript and NextJS but have used React for some time, but I can't grasp why this happens. The code below renders, but I'd like to understand why just some of the props goes undefined, after clearly being included. Also in this specific example I'd like to be able to use coverImage.width in the Image component width-property instead of hardcoding a value.
index.tsx
type Props = {
allPosts: Post[]
}
export default function Index({ allPosts }: Props) {
const heroPost = allPosts[0]
const morePosts = allPosts.slice(1)
return (
<>
<Layout>
<Head>
<title>SWG - Blog</title>
</Head>
<Container>
<Intro />
{heroPost && (
<HeroPost heroPost={heroPost} />
)}
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
</Container>
</Layout>
</>
)
}
export const getStaticProps = async () => {
const allPosts = (await getPosts()) || [];
return {
props: { allPosts },
}
}
hero-post.tsx. All heroPost-prop values are available here. Please see below image of console print as well (image link until I have 10 points on SO).
type Props = {
heroPost: {
title: string
coverImage: {
url: string
width: number
height: number
}
date: string
excerpt: string
author: Author
slug: string
}
}
const HeroPost = ({ heroPost }: Props) => {
return (
<section>
<div className="mb-8 md:mb-16">
<CoverImage title={heroPost.title} src={heroPost.coverImage.url} slug={heroPost.slug} coverImage={heroPost.coverImage} />
</div>
<div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28">
<div>
<h3 className="mb-4 text-4xl lg:text-5xl leading-tight">
<Link as={`/posts/${heroPost.slug}`} href="/posts/[slug]">
<a className="hover:underline">{heroPost.title}</a>
</Link>
</h3>
<div className="mb-4 md:mb-0 text-lg">
<DateFormatter dateString={heroPost.date} />
</div>
</div>
<div>
<p className="text-lg leading-relaxed mb-4">{heroPost.excerpt}</p>
<Avatar name={heroPost.author.name} picture={heroPost.author.picture} />
</div>
</div>
</section>
)
}
export default HeroPost
cover-image.tsx. Here's where the props of heroPost goes missing, but only the ones I've declared. Not "src" for example.
type Props = {
title: string
src: string
slug?: string
coverImage: {
url: string
width: number
height: number
}
}
const CoverImage = ({ title, src, slug, coverImage }: Props) => {
console.log(src);
//console.log(title);
console.log(coverImage);
const image = (
<>
<Image src={src} width={1495} height={841} />
</>
)
return (
<div className="sm:mx-0">
{slug ? (
<Link as={`/posts/${slug}`} href="/posts/[slug]">
<a aria-label={title}>{image}</a>
</Link>
) : (
image
)}
</div>
)
}
export default CoverImage
Apparently I can't post images since I'm a new member, but the above console.logs prints like this:
coverImage object exists
prop.src = correct src.value
props.coverImage = cover-image.tsx?0fdc:19 full coverImage object printed
Don't know where these react-devtools-backend lines come from but same thing here, both values exist.
react_devtools_backend.js:4082 correct src.value
react_devtools_backend.js:4082 full coverImage object printed
coverImage then goes undefined
cover-image.tsx?0fdc:19 undefined
but src in props still return its value
cover-image.tsx?0fdc:17 correct src.value
cover-image.tsx?0fdc:19 undefined
same thing with the lines from react_devtools_backend:
react_devtools_backend.js:4082 correct src.value
react_devtools_backend.js:4082 undefined
I've read numerous SO-threads and looked through the NextJS-docs but fail to realise what I'm doing wrong.
console.log of heroPost-object in hero-post component.

How to map Github GraphQL API commit responses to react-bootstrap Cards?

All the following code is in a custom component titled CommitCards.
Given the following gql query using React Apollo.
const GET_REPO_COMMITS = gql`
query GetRepoCommits($repoName: String!) {
repository(name: $repoName, owner: "FernandoH-G") {
defaultBranchRef {
target {
... on Commit {
history(first: 5) {
edges {
node {
pushedDate
message
url
}
}
}
}
}
}
}
}
`;
const repoName = props.rName
const { loading, error, data } = useQuery(GET_REPO_COMMITS, {
variables: { repoName },
});
if (loading) return (
<p>Loading...</p>
);
if (error) return (
<p>Error.</p>
);
I am able to get the last 5 commits from a given repository belonging to the given owner.
Given by the nature of how GraphQL's JSON response is structured, I feel the need to do the following:
const commits = data.repository.defaultBranchRef.target.history.edges
const innerCommits = commits.map(com =>(
com.node
))
Neither mapping over commits or innerCommits using more or less the following react-strap Card code:
return commits.map(com => {
<Card
key={com.node.url}
border="info">
<Card.Body>
<Card.Header as="h4"> {com.node.pushDate} </Card.Header>
<Card.Text> {com.node.message}</Card.Text>
</Card.Body>
</Card>
})
renders the cards on the screen.
Note that using the following test html does display the proper information, just as a single long string.
return(
<p>{commits.map( a => (
a.node.message
))}</p>
)
The component is called here:
<CardDeck>
<CommitCards rName={value} />
</CardDeck>
You might be missing the Bootstrap CSS that is required.
Add this import somewhere towards the top level of your app, like index.js or App.js:
import "bootstrap/dist/css/bootstrap.min.css";
See more: https://react-bootstrap.github.io/getting-started/introduction#stylesheets
So I figured it out...
return commits.map(com => {
<Card
key={com.node.url}
border="info">
<Card.Body>
<Card.Header as="h4"> {com.node.pushDate} </Card.Header>
<Card.Text> {com.node.message}</Card.Text>
</Card.Body>
</Card>
})
should have been this:
return commits.map(com => (
<Card
key={com.node.url}
border="info">
<Card.Body>
<Card.Header as="h4"> {com.node.pushDate} </Card.Header>
<Card.Text> {com.node.message}</Card.Text>
</Card.Body>
</Card>
))
Note the ( vs the { .

React-bootstrap cards not wrapping

My Code is:
import React, { useEffect, useState } from "react";
import styles from "./Cards.module.css";
import { CardDeck, Card } from "react-bootstrap";
const Cards = ({ animeArray }) => {
const [aanimeArray, setAnimeArray] = useState([]);
useEffect(() => {
setAnimeArray(animeArray);
}, [animeArray]);
if (!aanimeArray) {
return;
}
console.log("Anime Array", aanimeArray);
return (
<div className={styles.container}>
{aanimeArray === [] ? (
<h1>Search</h1>
) : (
<CardDeck>
{aanimeArray.map((anime) => {
return (
<Card>
<Card.Img variant = "top" src={anime.image_url} />
<Card.Body>
<Card.Title>{anime.title}</Card.Title>
</Card.Body>
<Card.Footer>
<small className="text-muted">{anime.rated}</small>
</Card.Footer>
</Card>
);
})}
</CardDeck>
)}
</div>
);
};
export default Cards;
I am not using any custom styling whatsoever.
The result of the above mentioned code is as seen on this image:
Image of the issue
You have to make the effort of making them wrap. In fact, as seen on the documentation, majority of the developers' examples includes the CSS property width with a value of 18rem.
Here is an example by leveraging minWidth:
const sampleStyle = {
minWidth: "20%",
flexGrow: 0
};
<Card style={sampleStyle}>
First thing.
aanimeArray === []
won't work since you are comparing an array with another array.
Best way in Javascript for this is to check the length of the array.
aanimeArray.length === 0
means it is an empty array.
About the styling I think you need to show us the CSS code as well. I'm not sure what CardDeck component does...

Access Gatsby Component from a function

I am trying to access a Gatsby component (Anime) from outside of it.
Can not figure out what instance name this would have or how to name it.
Here is my code:
import React from 'react'
import PropTypes from 'prop-types'
import PreviewCompatibleImage from '../components/PreviewCompatibleImage'
import Anime from 'react-anime';
import VisibilitySensor from 'react-visibility-sensor';
function onChange (isVisible) {
console.log('Element is now %s', isVisible ? 'visible' : 'hidden')
}
const FeatureGrid = ({ gridItems }) => (
<div className="columns is-multiline">
<VisibilitySensor onChange={onChange}>
<Anime delay={(e, i) => i * 100}
scale={[.1, .9]}
autoplay={false}>
{gridItems.map(item => (
<div key={item.text} className="column is-3">
<section className="section">
<div className="has-text-centered">
<div
style={{
width: '160px',
display: 'inline-block',
}}
>
<PreviewCompatibleImage imageInfo={item} />
</div>
</div>
<p>{item.text}</p>
</section>
</div>
))}
</Anime>
</VisibilitySensor>
</div>
)
FeatureGrid.propTypes = {
gridItems: PropTypes.arrayOf(
PropTypes.shape({
image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
text: PropTypes.string,
})
),
}
export default FeatureGrid
I want to get the animation to trigger from the onChange function.
How do I get the name or set the name of the Anime component so I can access it from the function?
Or is there another way I should address this?
Using a Gatsby starter netlify CMS as the base, so extending on their code, but seems that const is not the route I should take.
I want the animation to trigger when it becomes visible.
Any suggestions?
According to the docs react-visibility-sensor :
You can pass a child function, which can be convenient if you don't need to store the visibility anywhere
so maybe instead of using the onchange function you can just pass the isVisible parameter, something like:
<VisibilitySensor>
{({isVisible}) =>
<Anime delay={(e, i) => i * 100}
// the rest of your codes here ...
</Anime>
}
</VisibilitySensor>
Otherwise you can convert this function to a react component and set states, etc..

antd SubMenu "TypeError: Cannot read property 'isRootMenu' of undefined"

I use antd 3.15 and GraphQL to fetch data and generate a list of SubMenu and Menu.Item inside of Menu. However, I got the error message like this Uncaught TypeError: Cannot read property 'isRootMenu' of undefined I have no idea what is wrong with my code. isRootMenu is not a prop listed anywhere on the doc. ant.design/components/menu/#header and when I hardcoded all the SubMenu and Menu.List there is no problem. Can I iterate data from GraphQL to generate the SubMenu and Menu.List?
Can someone help me with this issue, please? Thank you! Here is my code:
import * as React from 'react';
import './SideNav.scss';
import { Menu, Icon } from 'antd';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
const FLOORS_QUERY = gql`
query {
getAllFloors {
id
floorName
rooms {
id
roomName
roomNumber
roomDescription
}
}
}
`;
export default class SideNav extends React.Component {
render() {
return (
<Menu theme="light" defaultSelectedKeys={['1']} mode="inline">
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <h4> loading... </h4>;
if (error) console.log(error);
console.log(data);
return (
<React.Fragment>
{data.getAllFloors.map((floor) => (
<SubMenu
key={floor.id}
title={
<span>
<Icon type="plus" />
<span>{floor.floorName}</span>
</span>
}
>
<React.Fragment>
{floor.rooms.map((room) => (
<Menu.Item key={room.id}>{room.roomNumber}</Menu.Item>
))}
</React.Fragment>
</SubMenu>
))}
</React.Fragment>
);
}}
</Query>
</Menu>
);
}
}
You should pass the props to the submenu.
const CustomComponent = (props) => (
<Menu.SubMenu title='SubMenu' {...props}>
<Menu.Item>SubMenuItem</Menu.Item>
</Menu.SubMenu>
)
so a solution to your question would be to do the following;
move the query outside of the containing menu
pass the props to the SubMenu
const FloorMapSubMenu = ({ id, floorName, rooms, ...other }) => {
return (
<Menu.SubMenu
key={id}
title={
<span>
<Icon type="plus" />
<span>{floorName}</span>
</span>
}
{...other} // notice the other props, this is were the 'isRootMenu' is injected from the <Menu> children
>
<React.Fragment>
{rooms.map((room) => (
<Menu.Item key={room.id}>{room.roomNumber}</Menu.Item>
))}
</React.Fragment>
</Menu.SubMenu>
)
}
class SideNav extends React.Component {
render() {
return (
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <h4> loading... </h4>
if (error) console.log(error)
console.log(data)
return (
<Menu theme='light' defaultSelectedKeys={['1']} mode='inline'>
{data.getAllFloors.map((floor, i) => (
<FloorMapSubMenu key={i} id={floor.id} floorName={floor.floorName} rooms={floor.rooms} />
))}
</Menu>
)
}}
</Query>
)
}
}
I found out the Ant design SubMenu needs to use the parent to check some properties like isRootMenu at
SubMenu.js:260
getPopupContainer = props.parentMenu.isRootMenu ? props.parentMenu.props.getPopupContainer : function (triggerNode) {
return triggerNode.parentNode;
}
In order to solve it you need to manually pass parent props into SubMenu like
<Menu.SubMenu {...this.props}/>
to solve the problem. Hope this helps u
Related Github issue item https://github.com/react-component/menu/issues/255
I had this issue while trying to add a <div> as a child of Menu. I just added an empty Menu.Item as the first child of my menu, and the error went away.
<Menu>
<Menu.Item style={{display: 'none'}} />
<div>...</div>
</Menu>
I ran into the same issue. It seems Antd does not allow to place arbitrary components into a Menu/SubMenu. My guess is that Menu.Item needs to get some props, which are passed from Menu/SubMenu to its children.
So you can either create a custom component that passes all props down, or remove the inner <React.Fragment> declaration (the one that is inside the SubMenu), which is not needed anyway.
I was able to make it work by putting the <Query> Component at the top:
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <Spin />;
if (error) console.log(error);
console.log(data);
return (
<Menu theme="light" defaultSelectedKeys={['1']} mode="inline">
{data.getAllFloors.map((floor) => (
<SubMenu
key={floor.id}
title={
<Link to="/{floor.id}">
<span>
<Icon type="plus" />
<span>{floor.floorName}</span>
</span>
</Link>
}
>
{floor.rooms.map((room) => (
<Menu.Item key={room.id} onClick={this.showRoomProfile}>
{room.roomNumber}
</Menu.Item>
))}
</SubMenu>
))}
</Menu>
);
}}
</Query>
According to the Typescript definitions the childrens of Menu should be of kind Item, SubMenu, ItemGroup or Divider. If you must place a different component on the Header, wrap the Menu and the desired component on a Header component component as such:
import { Layout } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
<Layout>
<Layout.Header>
<div className="logo" />
<Menu>
<Menu.Item key="1">nav 1</Menu.Item>
<Menu.Item key="2">nav 2</Menu.Item>
</Menu>
<Layout.Header>
</Layout>
I have run into the same issue. But my issues was I have using ternary condition to show some menu's dynamically inside part used the <></> element. That caused me this issue. Once removed that everything work fine.

Resources