Getting reference data from Graphql using Gatsby/Contentful - reactjs

I'm struggling to get the data from the reference part of my Graphql. I got a Contentful content model that includes references as you can see in the query below.
Now, I got a simple page like this, where I query data from Contentful:
import React from "react"
import { graphql } from "gatsby"
const PageTemplate = ({
data: {
islands: {
id,
title,
featuredActivities: { id, title },
},
},
}) => {
return (
<article key={id}>
<div>
<h3>{title}</h3>
</div>
</article>
<article key={featuredActivities.id}>
<div>
<h3>{featuredActivities.title}</h3>
</div>
</article>
)
}
export const query = graphql`
query GetSinglePage($slug: String) {
islands: contentfulPage (slug: {eq: $slug}) {
id
title
featuredActivities {
id
title
category
price
image {
fluid {
...GatsbyContentfulFluid
}
}
}
}
}
`
export default PageTemplate
And now I want to get the data for this part of the query:
featuredActivities {
id
title
category
price
image {
fluid {
...GatsbyContentfulFluid
}
}
}
I've tried this:
const PageTemplate = ({
data: {
islands: {
featuredActivities: { id },
featuredActivities: { title },
},
},
and added it like this into the :
<article key={featuredActivities.id}>
<h3>{featuredActivities.title}</h3>
</article>
But it's not working, does someone knows what I'm doing wrong?
Thanks in advance
[![enter image description here][1]][1]

And now I want to get the data for this part of the query
You can't destructure all items of the array like this:
const PageTemplate = ({
data: {
islands: {
featuredActivities: { id },
featuredActivities: { title },
},
},
Why? Because it's an array of objects and the structure ({}) doesn't match the type of the nested item. And, in case it was, and you will need to enter each specific position.
You may need to:
const PageTemplate = ({
data: {
islands: [{
featuredActivities: { id },
featuredActivities: { title },
}],
},
Notice the wrapping square brackets ([]).
However, as I said, it's not ideal since you need to print each specific position of the array of an unknown length. The best and optimal solution is to destructure until featuredActivities and loop through all elements:
const PageTemplate = ({
data: {
islands: {
id,
title,
featuredActivities,
},
},
}) => {
return <>
{featuredActivities.map(({title, id})=>{
return <div key={id}>{title}</div>
})}
</>
}
In that way, you can destructure inside the same loop in ({title, id}), since you are getting for each specific position (featuredActivity, the iterable variable), the title and the id (and so on for the rest of the needed fields).

Assuming that Islands and featuredActivities are not array or objects and you are getting the correct data from GraphQL query, you just need to correct how you destructure and use the values. Also since you cannot have clashing variable names, you will have to rename the variables from featuredActivities
const PageTemplate = ({
data: {
islands: {
id, title
featuredActivities: { id: featuredId , title: featuredTitle },
},
},
...
<article key={featuredId}>
<h3>{featuredTitle}</h3>
</article>

Related

how to get attributes from this javascript array

How to get attribute title from this array which has only one data record,
I just want to print title, for example
If I write console.log(data) it shows the result as shown in picture below, but when I write conosle.log(data[0].attributes.title) it shows 0 is undefined, how can I print title in console.log?
Full Code
const PostPage = ({ slug }) => {
// console.log(slug)
const QUERY = gql `query getPosts($slug: String!){
posts(filters: { slug: { eq: $slug } }) {
data {
id
attributes {
title
slug
description
}
}
}
}
`;
const { data, loading,error } = useQuery(QUERY,{ variables: {slug}});
console.log(data)
return (
<div class="card p-1">
</div>
);
};
export default PostPage;
export async function getServerSideProps({ query }) {
const slug = query.slug
console.log(slug)
return { props: { slug } };
}
Looks like you want:
console.log(data.posts.data[0].attributes.title)
updated based on your comment that the log in the image is of 'data'.
update 2: because data is populated using a hook that is probably asynchronous, you should expect it may be undefined or null until some promise is resolved.
if (data) {
console.log(data.posts.data[0].attributes.title)
} else {
console.log('Zzz... waiting for data')
}
posts.data[0].attributes.title = "Trump got some money..."
data[0].attributes.title = undefined

Gutenberg Block Variation Picker not working

I'm trying to add the BlockVariationPicker like in the WordPress Github example:
import { useSelect } from '#wordpress/data';
import {
__experimentalBlockVariationPicker as BlockVariationPicker,
store as blockEditorStore,
} from '#wordpress/block-editor';
const MyBlockVariationPicker = ( { blockName } ) => {
const variations = useSelect(
( select ) => {
const { getBlockVariations } = select( blocksStore );
return getBlockVariations( blockName, 'block' );
},
[ blockName ]
);
return <BlockVariationPicker variations={ variations } />;
};
In my edit function I'm adding:
{ MyBlockVariationPicker }
The block variation picker does not show.
I have already registered my bloc variations with scope block:
registerBlockVariation(
'my/testimonial',
[
{
name: 'testimonial-1',
title: 'Testimonial 1',
scope: ['block'],
attributes: {
example: 'testimonial-1'
},
},
{
name: 'testimonial-2',
title: 'Testimonial 2',
scope: ['block'],
attributes: {
example: 'testimonial-2'
},
}
]
);
The block variations should show in { MyBlockVariationPicker } but the don't. Unfortunately there isn't much documentation about this. How can we render the variations of a block using the Block Variation Picker as shown in the Github example?
Both the Columns and Query block use __experimentalBlockVariationPicker and its a really nice component/UI and I agree, it there aren't many examples of how to use it, most likely as its still 'experimental' and still likely to change.
I found that both the Columns and Query blocks display the BlockVariationPicker by checking if the current block (by clientId) contains any InnerBlocks; if there are none, the BlockVariationPicker is shown. When using this component in your own block, you will need some attribute or property to check whether or not a variation has been selected.
I've put together a basic/working example using the structure of your my/testimonial block + variations and based on how the BlockVariationPicker is implemented in Columns block:
import { get } from 'lodash';
import { useSelect } from '#wordpress/data';
import { registerBlockType, registerBlockVariation, store as blocksStore } from '#wordpress/blocks';
import { useBlockProps, __experimentalBlockVariationPicker as BlockVariationPicker } from '#wordpress/block-editor';
// Create our own BlockVariationPicker
const MyBlockVariationPicker = ({ name, setAttributes }) => { // Note: We need "name" and "setAttributes" from edit() props
const { blockType, variations, defaultVariation } = useSelect(
(select) => {
const { getBlockVariations, getBlockType, getDefaultBlockVariation } = select(blocksStore);
return {
blockType: getBlockType(name),
defaultVariation: getDefaultBlockVariation(name, 'block'),
variations: getBlockVariations(name, 'block')
};
},
[name]
);
return <BlockVariationPicker
variations={variations}
icon={get(blockType, ['icon', 'src'])}
label={get(blockType, ['title'])}
onSelect={(nextVariation = defaultVariation) => {
if (nextVariation.attributes) {
setAttributes(nextVariation.attributes); // Use setAttributes to set the selected variation attributes
}
}}
/>;
};
// Register the Block Variations
registerBlockVariation(
'my/testimonial',
[
{
name: 'testimonial-1',
title: 'Testimonial 1',
icon: 'admin-comments', // Added icon so the variation is visibly different (optional)
scope: ['block'],
attributes: {
example: 'testimonial-1'
},
isDefault: true
},
{
name: 'testimonial-2',
title: 'Testimonial 2',
icon: 'admin-links',
scope: ['block'],
attributes: {
example: 'testimonial-2'
},
}
]
);
registerBlockType('my/testimonial', {
title: 'My Testimonial',
keywords: ['testimonial'],
icon: 'admin-post',
attributes: {
example: {
type: "string", // no default set, example is "undefined"
}
},
edit(props) {
const { attributes, setAttributes } = props;
// If example is undefined, show Variation Picker
if (attributes.example === undefined) {
return (
<MyBlockVariationPicker {...props} />
);
}
// Otherwise show the Editor
return (<div {...useBlockProps()}><h2>{attributes.example}</h2></div>);
},
save: ({ attributes }) => {
return <div {...useBlockProps.save()}><h2>{attributes.example}</h2></div>;
}
})
If you build the above javascript, the resulting block allows you to pick from the two variations on insertion:

Is there a way to use gatsby-awesome-pagination with dynamically filtered data?

I am using gatsby-awesome-pagination. In my CMS (Contentful) I have a number of Profile data types each with category fields. I am creating a paginated listing for each category in gatsby-node.js like such:
// fetch data from CMS & create unfiltered paginated list
const directory = path.resolve('./src/templates/directory.js')
const profiles = result.data.allContentfulProfile.edges
awesomePagination.paginate({
createPage, // The Gatsby `createPage` function
items: profiles, // An array of objects
itemsPerPage: 12, // How many items you want per page
pathPrefix: '/directory', // Creates pages like `/blog`, `/blog/2`, etc
component: directory, // Just like `createPage()`
})
// create categorised directory pages
const categories = result.data.allContentfulProfile.categories
categories.forEach((category) => {
const categoryProfiles = profiles.filter(
(profile) => profile.node.category === category
)
awesomePagination.paginate({
createPage,
items: categoryProfiles,
itemsPerPage: 12,
pathPrefix: `/directory/type/${slugify(category, {
lower: true,
})}`,
component: directory,
context: {
category: slugify(category, { lower: true }),
},
})
})
Within directory.js itself, I am querying for data like this:
export const directoryQuery = graphql`
query DirectoryQuery($limit: Int!, $skip: Int!) {
paginatedProfiles: allContentfulProfile(
sort: { fields: [createdAt], order: ASC }
skip: $skip
limit: $limit
) {
edges {
node {
// fields
}
}
}
}
`
Up til this point I am basically just following the tutorial for gatsby-awesome-pagination and there is nothing surprising going on here. Now is where I combine this with a filter, which updates state with the selectedCategory via a simple <select>.
Then I go off-piste. I am preparing the data within directory.js by doing:
const profiles = get(this, 'props.data.paginatedProfiles.edges')
prepareIndexData(items, filterField, filterItem) {
let sourceItems = null
if (filterItem) {
if (filterField === 'category') {
sourceItems = items.filter(
(item) =>
slugify(item.node[`${slugify(filterField, { lower: true })}`], {
lower: true,
}) === filterItem
)
}
} else {
sourceItems = items
}
return sourceItems
}
And finally displaying the data on `directory.js` by doing:
render() {
return (
<div width={12}>
{this.prepareIndexData(profiles, 'category', category).map((profile) => (
<ProfileCard key={profile.node.slug} profile={profile} />
))}
</div>
)
}
This correctly filters by category on the Profiles, but only on those Profiles in the first 12 Profiles. This is because despite the fact I have built the page correctly in gatsby-node.js and passed $skip and $limit appropriately, I am still essentially querying on the original unfiltered paginated data in the form of paginatedProfiles in directory.js.
Is this the wrong way to go about this? Can this even be done on a static site or am I better off migrating to Next.js?
This is because despite the fact I have built the page correctly in
gatsby-node.js and passed $skip and $limit appropriately, I am still > essentially querying on the original unfiltered paginated data in the form of > paginatedProfiles in directory.js.
You hit the nail. This is exactly what's happening
The way you are using gatsby-node.js is just to create dynamic pages from a CMS (Contentful) source, basically, without knowing what will be each category name. So, to achieve what you are trying to do, I think you just need to create a new page query in your directory.js, without $limit and $skip filters, but filtering by category (to only get categories) and use that data as you are doing now. Something like:
const profiles = get(this, 'props.data.yourNewQuery.edges')
prepareIndexData(items, filterField, filterItem) {
let sourceItems = null
if (filterItem) {
if (filterField === 'category') {
sourceItems = items.filter(
(item) =>
slugify(item.node[`${slugify(filterField, { lower: true })}`], {
lower: true,
}) === filterItem
)
}
} else {
sourceItems = items
}
return sourceItems
}
And finally displaying the data on `directory.js` by doing:
render() {
return (
<div width={12}>
{this.prepareIndexData(profiles, 'category', category).map((profile) => (
<ProfileCard key={profile.node.slug} profile={profile} />
))}
</div>
)
}
export const directoryQuery = graphql`
query DirectoryQuery($limit: Int!, $skip: Int!) {
paginatedProfiles: allContentfulProfile(
sort: { fields: [createdAt], order: ASC }
skip: $skip
limit: $limit
) {
edges {
node {
// fields
}
}
}
yourNewQuery: allContentfulProfile(filter: { contentType: { in: ["category"] } }){
edges {
node {
// fields
}
}
}
}
`
Note: change the filter to a new one that matches your use case.

Ordering posts in Contentful by date with different Content types

This is my first project using GatsbyJS and Contentful. Right now I have several posts on the site with different Content Models. For the sake of simplicity let's say I have some Content types that are Photos and others that are Video Embeds. I am using GraphQL to fetch the posts...
I have different components for each type.
Photos (also called the PhotoSection)
const data = useStaticQuery(graphql`
query {
allContentfulImage(sort: { fields: date, order: DESC }) {
edges {
node {
image {
description
file {
url
}
}
}
}
}
}
`)
Video Embeds (also called the Film Section)
const data = useStaticQuery(graphql`
query {
allContentfulVideoEmbeds(sort: { fields: date, order: DESC }) {
edges {
node {
embedURL
}
}
}
}
`)
I then compile all of the components in another component called Blog
const Blog = () => {
return (
<div>
<AudioSection />
<FilmSection />
<PhotoSection />
</div>
)
}
export default Blog
The end product is that the posts are in descending order by date BUT they are also organized by their section / Content type. If you follow the codeblock the order is AudioSection -> FilmSection -> PhotoSection. I want them to be ordered by Date (latest first) regardless of the Content type.
Hopefully this makes sense. Not really sure what to do here?
Thank you in advance
Edit... this is post attempting what was suggested. i cut out some of the bulkier sections and but left the PhotoComponent as an example
const BlogTwo = () => {
const data = useStaticQuery(graphql`
query {
music: { bla bla bla }
videos: { bla bla bla }
images: allContentfulImage(sort: { fields: date, order: DESC }) {
nodes {
type: __typename
image {
description
file {
url
}
}
}
}
`)
const dataForDisplay = [data.music, data.images, data.videos].sort(
(a, b) => b.date - a.date
)
const componentTypeMap = {
ContentfulMusicAndArt: MusicComponent,
ContentfulImage: PhotoComponent,
ContentfulVideoEmbeds: FilmComponent,
}
const MusicComponent = () => {
return (
<div>
bla bla bla
</div>
)
}
const PhotoComponent = () => {
return (
<div className={`${blogStyles.blogDiv}`}>
<div className={`${blogStyles.blogPost} ${blogStyles.image}`}>
<img
src={data.images.nodes.image.file.url}
alt={data.images.nodes.image.description}
/>
</div>
</div>
)
}
const FilmComponent = () => {
return (
<div>
bla bla bla
</div>
)
}
return (
<div>
{dataForDisplay.map(({ type, props }) =>
React.createElement(componentTypeMap[type], props)
)}
</div>
)
}
export default BlogTwo
Instead of wrapping each content type in a WhateverSection component, combine all three arrays of content into a single array, sort it, and then loop through the combined array rendering the relevant component for each entry.
To make this more sensible, I'm going to refactor your use of static queries by hoisting them up into the blog component, combining them, and adding GraphQL aliases to make retrieving the data less verbose.
const Blog = () => {
const data = useStaticQuery(graphql`
query {
images: allContentfulImage(sort: { fields: date, order: DESC }) {
nodes {
type: __typename
date
image {
description
file {
url
}
}
}
}
videos: allContentfulVideoEmbeds(sort: { fields: date, order: DESC }) {
nodes {
type: __typename
date
embedURL
}
}
}
`)
const dataForDisplay = [...data.images.nodes, ...data.videos.nodes].sort((a, b) => b.date - a.date)
return (
<div>
{dataForDisplay.map(({ type, ...props }) =>
React.createElement(componentTypeMap[type], props)
)}
</div>
)
}
export default Blog
const componentTypeMap = {
ContentfulImage: PhotoComponent,
ContentfulVideoEmbed: FilmComponent,
}

GatsbyJS filter queries by location pathname

I'm building a blog with products, each product belongs to several categories. You can click a certain category, and it will take you to a page that only displays products that have that category.
Right now, i'm getting all products on every "category page", and use JS to filter the products, but i want to only load data that i need from the start.
Issue is that the variable that i'm suppost to filter by, is a variable that i compute from location.pathname; (I removed a lot of not relevant code from the snippet)
How can i find a syntax that allows me to add another filter to this query, that uses the "category" variable from this template component?
render() {
const { classes } = this.props
const posts = get(this, 'props.data.allContentfulBlog.edges')
const edges = this.props.data.allContentfulBlog.edges
const category = location.pathname
return (
<div className={classes.container}>
</div>
)
}
query categoryPostQuery {
allContentfulBlog(
filter: { node_locale: { eq: "en-US" } }
sort: { fields: [date], order: DESC }
) {
edges {
node {
id
categories
date(formatString: "DD MMMM, YYYY")
slug
}
}
}
}
I am supposed to enter the categories field, which is an array, and check if it includes the "category" variable.
This can be accomplished using Gatsby's query variables:
query categoryPostQuery($category: String) {
allContentfulBlog(
filter: { node_locale: { eq: "en-US" }, categories: { in: [$category] } }
sort: { fields: [date], order: DESC }
) {
edges {
node {
id
categories
date(formatString: "DD MMMM, YYYY")
slug
}
}
}
}
And the category variable can be set using the context option in gatsby-node.js createPages:
createPage({
path,
component: categoryTemplate,
// If you have a layout component at src/layouts/blog-layout.js
layout: `blog-layout`,
context: {
category: 'games'
},
})

Resources