Skip to content
Menu

Simple Filtering with Craft CMS and Gatsby

Filtering elements on a website is a pretty common piece of functionality. For example, filtering blog posts or products by a specific category to narrow results. In this post I'll show you how to pull in news posts and categories from Craft CMS and create a simple filtering app with React hooks!

Firstly, we need to fetch our categories and posts through a GraphQL query -

{
craft {
categories: categories(group: "newsCategories") {
... on craft_newsCategories_Category {
id
title
slug
}
}
allPosts: entries(section: "news") {
... on craft_news_news_Entry {
id
title
newsCategory {
slug
}
}
}
}
}

and then loop over the data in our template -

// Data returned from our query
const { categories, allPosts } = data.craft
return (
<Layout>
<h2>Filter by category -</h2>
<select>
<option value="all">All</option>
{categories.map(category => (
<option key={category.id} value={category.slug}>
{category.title}
</option>
))}
</select>
<h2>Posts</h2>
<div>
{allPosts.map(post => (
<Post key={post.id} title={post.title} />
))}
</div>
</Layout>
)

Next we can create our state variables using the State Hook, and create a function to filter the posts when our select element changes -

// Create state for our posts list and the selected value
const [posts, setPosts] = useState(allPosts)
const [selected, setSelected] = useState("all")
// Our function that does the filtering
const filterPosts = value => {
let posts = allPosts
if (value !== "all") {
posts = allPosts.filter(post =>
post.newsCategory.find(category => category.slug === value)
)
}
setSelected(value)
setPosts(posts)
}

Now we just need to update the select element to call the function when updated and loop over our posts state array, which will then update automatically -

return (
<select onChange={e => filterPosts(e.target.value)} value={selected}>
...
</select>
{posts.map(post => (
<Post key={post.id} title={post.title} />
))}
)

Bonus! Animating with Framer Motion

We can use the Framer Motion library to animate the posts when the state is updated. If we wrap the posts in AnimatePresence and use a motion component for each post, we can then animate the posts when our state is updated.

import { motion, AnimatePresence } from "framer-motion"
return (
...
<AnimatePresence>
{posts.map(post => (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
positionTransition
key={post.id}>{post.title}</div>
))}
</AnimatePresence>
)

Adding permalinks

We can add permalinks for pre-filtered pages (e.g. /news/archive) by creating a page from each of the category nodes. We can then pass the category slug as page context to then filter the posts when the page is loaded.

To do this, we need to update our gatsby-node.js API file -

const path = require("path")
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
return new Promise((resolve, reject) => {
resolve(
graphql(
`
{
craft {
categories(group: "newsCategories") {
... on craft_newsCategories_Category {
slug
}
}
}
}
`
).then(result => {
// Errors if we can't conntect to the GraphQL endpoint, e.g. if the CMS is down
if (result.errors) {
reject(result.errors)
}
result.data.craft.categories.forEach(category => {
createPage({
path: `/news/${category.slug}/`,
component: path.resolve("./src/templates/news.js"),
context: {
slug: category.slug,
},
})
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
resolve()
})
})
)
})
}

We can look for this slug when the component is first mounted with the Effect hook to filter the posts, if the slug exists.

useEffect(() => {
if (pageContext.slug) {
filterPosts(pageContext.slug)
}
}, [])

Alternatively, we could use React Router URL parameters to access pieces of the URL to fetch the slug used to filter the list.

The same method could be used to filter any type of data you have passed into your React application, including search results, post listings, locations, team members etc.

If you have a large amount of data, you may want to paginate the results. You could add this easily using a library such as react-paginate and doing some calculations with number of items returned in the data.

One of my favourite things about using Gatsby is having the best features and power of React available to build interactive components with ease. You can build a website that has the feel of a web app with awesome dynamic components.