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'
},
})
Related
I want to create a slug for the url as soon as the user adds an event from the frontend. The slug is based on the name of the event. How to do that in V4 as the old method does not work now?
Slug creation link - old version
By following the article, it seems that you are trying to add lifecycle events to a model. You would need to make the following modifications to the article to make it work for v4.
After the creation of the article model via the admin dashboard, instead of adding the following file:
./api/article/models/Article.js
add:
./src/api/article/content-types/article/lifecycles.js
With the following:
const slugify = require('slugify');
module.exports = {
async beforeCreate(event) {
if (event.params.data.title) {
event.params.data.slug = slugify(event.params.data.title, {lower: true});
}
},
async beforeUpdate(event) {
if (event.params.data.title) {
event.params.data.slug = slugify(event.params.data.title, {lower: true});
}
},
};
Also the api endpoint changed in v4 so you would need to use:
GET /api/articles?filters[slug]=my-article-slug
This seem to work for me
Settings > Roles > Public > Slugify (checkbox findSlug)
config/plugins.js
module.exports = ({ env }) => ({
slugify: {
enabled: true,
config: {
contentTypes: {
page: {
field: "slug",
references: "name",
},
post: {
field: "slug",
references: "name",
},
category: {
field: "slug",
references: "name",
},
},
},
},
});
graphql
const POSTS = gql`
query GetPosts {
posts {
... on PostEntityResponseCollection {
data {
__typename
id
attributes {
__typename
name
slug
content
featuredImage {
data {
__typename
id
attributes {
url
alternativeText
caption
}
}
}
createdAt
}
}
}
}
}
`;
const POST = gql`
query GetPost($slug: String!) {
findSlug(modelName: "post", slug: $slug, publicationState: "live") {
... on PostEntityResponse {
data {
__typename
id
attributes {
createdAt
name
slug
content
seo {
__typename
id
title
description
blockSearchIndexing
}
categories {
__typename
data {
__typename
id
attributes {
__typename
name
slug
}
}
}
}
}
}
}
}
`;
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.
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>
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,
}
I am building an application with React, Apollo and Prsima that allows users to filter cars by model, brand, price... I know (for example) how to filter cars by brand:
const GET_CARS = gql`
query FilterCars($brandId: ID){
cars(where: {
model : { brand: { id: $brandId } }
}) {
id
model {
name
brand {
name
}
horses
}
year
km
price
...
}
`;
And in the component:
const CarList = (props) => {
const { data, loading, error } = useQuery(GET_CARS, {
variables: {
brandId: "exampleBrandId"
}
})
...
}
The problem is, that some parameters are optional: maybe the user does not care about the brand, or the model, or the price... So then all cars should appear: If no brand is selected, cars of all brands should appear; If no price is selected, cars of all prices should appear...
How can I do that? Something like:
query FilterCars($brandId: ID){
cars(where: {
model : { brand: { id: $brandId || all_brand_ids } }
}) {
...
}
}
I have investigated a and found a possible solution, but the custom input that the post refers to is not generated in my prisma.
getCars(parent, args, {prisma}, info){
const queryArgs ={}
if(args.brandId){
queryArgs ={
where: {
model: {
id: args.brandId
}
}
}
}
if(args.price){
queryArgs={
...queryArgs,
where: {
...queryArgs.where
//What every you want to add
}
}
}
return prisma.query.cars(queryArgs, info)
}
You can check on the Backend your args. And create a flexible obj to query for...
The answer from #Toiz is feasible. However, what if the number of parameters increases so that multiple if-statements are needed?
I suggest using undefined.
As mentioned in prisma official docs here, you can use undefined to optioanally exclude a field from query.
For example,
where: {
model : {
brand: {
id: $brandId != null ? $brandId : undefined
}
}
}
const GET_CARS = gql`
query FilterCars($brandId: ID){
cars(where: {
model : { brand: { id: $brandId } }
}) {
id
model {
name
brand {
name
}
horses
}
year
km
price
}
`;
const CarList = (props) => {
const { data, loading, error } = useQuery(GET_CARS, {
variables: {
brandId: "exampleBrandId"
}
})
}
brandId: "exampleBrandId" - this should be dynamic, and one of the options could be something like "All Brands", which would then query all brands.