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 {idtitleslug}}allPosts: entries(section: "news") {... on craft_news_news_Entry {idtitlenewsCategory {slug}}}}}
and then loop over the data in our template -
// Data returned from our queryconst { categories, allPosts } = data.craftreturn (<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 valueconst [posts, setPosts] = useState(allPosts)const [selected, setSelected] = useState("all")// Our function that does the filteringconst filterPosts = value => {let posts = allPostsif (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.divinitial={{ opacity: 0 }}animate={{ opacity: 1 }}exit={{ opacity: 0 }}positionTransitionkey={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 } = actionsreturn 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 downif (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/resolveresolve()})}))})}
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.