In this article, I will discuss how to work with the Apollo Client, the new GraphQL client library that is used in the Faust.js framework.
The New Faust.js
Faust.js is the front-end JavaScript framework built on top of Next.js, created to make developing headless WordPress sites easier. The idea of this new version is to make a parity between the WordPress world that a developer might be used to but replicate it as much as possible in the JavaScript front-end world.
The Apollo Client
Faust uses Apollo for its GraphQL client. TheApollo client is a data-fetching library for JavaScript that enables you to manage both local and remote data with GraphQL. It has features you can use for fetching, modifying app data, and caching. We are going to focus on how to use it in Faust.
Usage in Faust.js
Within Faust, you can use the client in your components and pages. Take this example here from theFaust docs:
import { gql, useQuery } from '@apollo/client';export default function Page(props) { const { data } = useQuery(Page.query); const { title, description } = data.generalSettings; return( <h1>{title}</h1> <p>{description}</p> )}Page.query = gql` query { generalSettings { title description } }`
Let’s go over what is happening in this file. At the top of the file,graphql
and theuseQuery
hook from Apollo is being imported into the file. Then we have a default function calledPage
that will render thetitle
anddescription
of the general settings from your WPGraphQL API. After passing the props into thePage
function, we create a variable that contains thedata
object that is the destructured response from theuseQuery
hook provided by Apollo. This then takesPage.query
as a parameter.
At the bottom of the file is our actual GraphQL query whichPage.query
is coming from as a parameter. We are calling in the constant variable with theuseQuery
hook.
Back above the return statement, we have an object that contains the destructured fields queried which are thetitle
anddescription
from the data in WordPressgeneralSettings
.
It co-locates the GraphQL query within the same file. You can also import and use queries from a file.
The benefit of Faust is that you don’t have to create the client object in your codebase. It is automatically created when you first import the@faustwp-core
package. Instead, you can customize it by using a plugin filter.
Custom plugin filter 🔌: Pagination example
For this article, let’s create a custom plugin filter to use cursor-based pagination in Faust.js with Apollo. If you need a deeper understanding of pagination, please check out this article I wrote onPagination in Headless WP.
The first thing we need to do is go into thecomponents
folder at the root of the Faust project. In the components folder, create a file calledLoadMorePost.js
and paste this code block in the file:
import { useQuery, gql } from "@apollo/client";import Link from "next/link";const GET_POSTS = gql` query getPosts($first: Int!, $after: String) { posts(first: $first, after: $after) { pageInfo { hasNextPage endCursor } edges { node { id databaseId title slug } } } }`;const BATCH_SIZE = 5;export default function LoadMorePost() { const { data, loading, error, fetchMore } = useQuery(GET_POSTS, { variables: { first: BATCH_SIZE, after: null }, }); console.log(data); if (error) { return <p>Sorry, an error happened. Reload Please</p>; } if (!data && loading) { return <p>Loading...</p>; } if (!data?.posts.edges.length) { return <p>no posts have been published</p>; } const posts = data.posts.edges.map((edge) => edge.node); const haveMorePosts = Boolean(data?.posts?.pageInfo?.hasNextPage); return ( <> <ul style={{ padding: "0" }}> {posts.map((post) => { const { databaseId, title, slug } = post; return ( <li key={databaseId} style={{ border: "2px solid #ededed", borderRadius: "10px", padding: "2rem", listStyle: "none", marginBottom: "1rem", }} > <Link href={`${slug}`}>{title}</Link> </li> ); })} </ul> {haveMorePosts ? ( <form method="post" onSubmit={(event) => { event.preventDefault(); fetchMore({ variables: { after: data.posts.pageInfo.endCursor } }); }} > <button type="submit" disabled={loading}> {loading ? "Loading..." : "Load more"} </button> </form> ) : ( <p>✅ All posts loaded.</p> )} </> );}
Let’s break this file down into chunks. At the top of the file, I am importing theuseQuery
hook andgql
provided by the Apollo client that I am using as well asnext/link
from Next.js. We will need these imports in this file.
The next thing you see is the query we created with cursor-based pagination.
const GET_POSTS = gql` query getPosts($first: Int!, $after: String) { posts(first: $first, after: $after) { pageInfo { hasNextPage endCursor } edges { node { id databaseId title slug } } } }`;const BATCH_SIZE = 5;
After that, I have a default components function calledLoadMorePost
. In this function, I am utilizing theuseQuery
hook in Apollo to pass in my query calledGET_POSTS
from the top of the file.
Next, I have variables that I pass in, which is thebatch size
I defined to be5
, and after null which tells the query to start from the beginning of the batch. This function gets fired off each time the user clicks the“load more”
button.
export default function LoadMorePost() { const { data, loading, error, fetchMore } = useQuery(GET_POSTS, { variables: { first: BATCH_SIZE, after: null }, }); console.log(data); if (error) { return <p>Sorry, an error happened. Reload Please</p>; } if (!data && loading) { return <p>Loading...</p>; } if (!data?.posts.edges.length) { return <p>no posts have been published</p>; }
There are 2 variables that get set next. The first variable isposts
which is taking the data that Apollo gives us back and drilling down into it with theposts
and their nested data. The second variable ishaveMorePosts
which checks if we have more posts to load but if there are no moreposts
we will have to execute something else.
const posts = data.posts.edges.map((edge) => edge.node);const haveMorePosts = Boolean(data?.posts?.pageInfo?.hasNextPage);
So now we can display our posts with a return statement with some data drilling within the levels of nesting that comes from the query.
Focusing now on the return statement, we have a<ul>
tag. Within that tag, we are mapping over posts and returning a single post with adatabaseId
,title
, and itsslug
. For each of those, we are displaying a list item with a<li>
tag. This list item will have a title that has a link to the actual individual blog post’s page.
return ( <> <ul style={{ padding: "0" }}> {posts.map((post) => { const { databaseId, title, slug } = post; return ( <li key={databaseId} style={{ border: "2px solid #ededed", borderRadius: "10px", padding: "2rem", listStyle: "none", marginBottom: "1rem", }} > <Link href={`${slug}`}>{title}</Link> </li> ); })} </ul>
Lastly, we have to add a“load more”
button. This button when clicked will load the next batch of posts from the cursor’s point. In order to do this, we take ourhaveMorePosts
boolean and if we do have more, we will display a form with a button inside of it. When that button is clicked, we have aonSubmit
handler that calls thefetchMorefunction
in Apollo and passes in the variable called after that grabs the current end cursor, which is the unique ID that represents the last post in the data set to grab the next 5 after that end cursor.
{haveMorePosts ? ( <form method="post" onSubmit={(event) => { event.preventDefault(); fetchMore({ variables: { after: data.posts.pageInfo.endCursor } }); }} > <button type="submit" disabled={loading}> {loading ? "Loading..." : "Load more"} </button> </form> ) : ( <p>✅ All posts loaded.</p> )} </> );}
Now that we have created our component in Faust for a paginated page to load posts in batches of 5, the next step is to create a page to test this out. Navigate to the pages directory in the root of the project and create a file calledpagination.js
.
In that file, copy and paste this code block in:
import Head from "next/head";import LoadMorePost from "../components/LoadMorePost";export default function LoadMore() { return ( <> <Head> <title>Load More</title> </Head> <main> <h1>Load More Example</h1> <LoadMorePost /> </main> </> );}
In this file, we are importing the component into this file and exporting it as a default function, returning it to render on the page.
Custom Plugin Creation in Faust for the client object
The Apollo client can implementrelay-style pagination with the relay spec using merge and read functions, which means all the details ofconnections
,edges
andpageInfo
can be abstracted away, into a single, reusable helper function. WPGraphQL follows the relay spec as well.
What we need to do is create a plugin for relay-style pagination in order for Faust to utilize thatfunction from Apollo.
In order tocreate a Faust plugin, we are going to use itsapply
method which is a JavaScript class. Theapply
method has a parameter calledhooks
which is passed from@wordpress/hooks.
The first step is to go to the root of the project and create a folder calledplugins
. In this plugin folder, create a file calledRelayStylePagination.js
. Copy and paste this code block into that file:
import { relayStylePagination } from "@apollo/client/utilities";export class RelayStylePagination { apply(hooks) { const { addFilter } = hooks; addFilter("apolloClientInMemoryCacheOptions", "faust", (options) => { return { ...options, typePolicies: { ...options.typePolicies, RootQuery: { ...options.typePolicies.RootQuery, fields: { posts: relayStylePagination(), }, }, ContentType: { fields: { contentNodes: relayStylePagination(), }, }, }, }; }); }}
At the top of the file, we import therelayStylePagination
function from Apollo’s utility library. Following that, we create aclass component
which is the basic syntax used in a Faust plugin.
Next, we have anapply
method with thehooks
parameter which is an object that gives you a function calledaddFilter
.TheaddFilter
function allows us to modify the Apollo Client’sInMemoryCacheOptions
.
In the next few lines of code, we are taking theaddFilter
hook and calling thememory cache
functionoptions
. Theoptions
are coming from theApollo Client Cache configuration. Theseoptions
allow for configuring the cache’s behavior to suit our use case. In this article, we are defining the configuration for pagination. Thefaust
namespace follows that.
Next, we spread through ouroptions
which is a callback function that can return theinMem cache options
as they are. We can also merge in our own withtypePolicies
that we define along with other specific queries we need to merge in the future.ThetypePolicies
is a class that defines a policy of your type in Apollo. Here, we are adding it to theRootQuery
option:
addFilter("apolloClientInMemoryCacheOptions", "faust", (options) => { return { ...options, typePolicies: { ...options.typePolicies, RootQuery: { ...options.typePolicies.RootQuery
The last few lines of code are where we are defining our fields of theposts
type. Once those are defined, we can now use the relay spec used by WPGraphQL to tell Apollo and its spec in our pagination method to go ahead and append the previous and next list together using cursor-based pagination by calling therelayStylePagination
function provided. Another thing to note is I also addedContentType
to expose thecontentNodes
fields in case of using something like search functionality.
fields: { posts: relayStylePagination(), }, }, ContentType: { fields: { contentNodes: relayStylePagination(), }, }, }, }; }); }}
Lastly, we need to go to ourfaust.config.js
file to imbed it into theexperimentalPlugins
key as a value like so:
import { setConfig } from "@faustwp/core";import templates from "./wp-templates";import possibleTypes from "./possibleTypes.json";import { RelayStylePagination } from "./plugins/RelayStylePagination";/** * @type {import('@faustwp/core').FaustConfig} **/export default setConfig({ templates, experimentalPlugins: [new RelayStylePagination()], possibleTypes,});
Generating possible types in Apollo
Before you run the dev server, make sure you type the commandnpm run generate
since we updated the schema. The Apollo client requires that we provide apossibleTypes
object that maps over our updated schema. The Faust framework provides the command script for you.
Stoked! Once you have added it to the config file in the plugins, this will allow you to have the pagination you expect when the user clicks your load more button and expects the next batch of posts!!
Previous and Next Post Link extension of WPGraphQL
The last piece I want to cover is using the WP Template to your advantage to create components and where those GraphQL queries will live and co-locate.
Out of the box, WPGraphQL does not have the ability to expose and query forprevious
andnext
post.The pagination fields plugin will allow us to modify the GraphQL schema and accomplish this.
Once this plugin is downloaded into your WP admin, you can now query WPGraphQL for the links. The query I made looks like this which you can run in GraphiQL IDE with theslug
as the query variable:
query getPost($slug: ID!) { post(id: $slug, idType: SLUG) { databaseId title content slug previousPost { title slug } nextPost { title slug } } }
I am querying for a single post via its slug as the variable. At the bottom of the query is where I am able to also query for the previous and next posts with the single post query as the starting point.
Finalizing Previous and Next posts in Faust.js
Back in my faust.js app, navigate to the components directory at the root of the project. From there, go to theFooter
folder and create a file calledPaginatedFooter.js
and copy this block and paste it in:
import Link from "next/link";export default function PaginatedFooter({ post }) { const { previousPost, nextPost } = post; return ( <> <footer style={{ display: "flex", textAlign: "center" }}> {previousPost ? ( <div style={{ border: "2px solid #ddd", padding: "1rem", }} > <Link href={`${previousPost.slug}`}> <a>👈 {previousPost.title}</a> </Link> </div> ) : null} {nextPost ? ( <div style={{ border: "2px solid #ddd", padding: "1rem", marginLeft: "1rem", }} > <Link href={`${nextPost.slug}`}> <a>{nextPost.title} 👉</a> </Link> </div> ) : null} </footer> </> );}
At the top of the file, I am importingLink
fromnext/link
using Next.js’s client-side navigation to link pages.
Then I have a default function calledPaginatedFooter
that is accepting thepost
data as its props. The following is a constant variable that destructures thepost
props ofpreviousPost
andnextPost
which we now have exposed and are querying from WPGraphQL.
export default function PaginatedFooter({ post }) { const { previousPost, nextPost } = post;
Lastly, I have a return statement wrapped in a fragment that will render afooter
tag. In thisfooter
tag, I havepreviousPost
, and if the post does exist, we display that previouspost title
.
Usingnext/link
, the user has access to a clickable link to route them to that previous post page. Otherwise, if we do not have a previous post, it will rendernull
, and nothing will appear.
After that, we have a similarJSX
callednextPost
which does the exact same thing aspreviousPost
except it will show and render the next post.
return ( <> <footer style={{ display: "flex", textAlign: "center" }}> {previousPost ? ( <div style={{ border: "2px solid #ddd", padding: "1rem", }} > <Link href={`${previousPost.slug}`}> <a>👈 {previousPost.title}</a> </Link> </div> ) : null} {nextPost ? ( <div style={{ border: "2px solid #ddd", padding: "1rem", marginLeft: "1rem", }} > <Link href={`${nextPost.slug}`}> <a>{nextPost.title} 👉</a> </Link> </div> ) : null} </footer> </> );}
This component is now ready to be embedded into asingle-page
WP template.
Faust.js WordPress templates
Faust.js provides a JavaScript version of theWordPress template hierarchy and its system. Let’s utilize this system for our paginated footer component with thesingle.js
file which is the WP template component that renders the single-post details page.
Navigating to thewp-templates/single.js
, copy and paste over the current boilerplate code already in this file with this block:
import { gql } from "@apollo/client";import PaginatedFooter from "../components/Footer/PaginatedFooter";import * as MENUS from "../constants/menus";import { BlogInfoFragment } from "../fragments/GeneralSettings";import { Header, Main, Container, EntryHeader, NavigationMenu, ContentWrapper, FeaturedImage, SEO,} from "../components";export default function Component(props) { // Loading state for previews if (props.loading) { return <>Loading...</>; } const { title: siteTitle, description: siteDescription } = props?.data?.generalSettings; const primaryMenu = props?.data?.headerMenuItems?.nodes ?? []; const footerMenu = props?.data?.footerMenuItems?.nodes ?? []; const { title, content, featuredImage, date, author } = props.data.post; return ( <> <SEO title={siteTitle} description={siteDescription} imageUrl={featuredImage?.node?.sourceUrl} /> <Header title={siteTitle} description={siteDescription} /> <Main> <> <EntryHeader title={title} image={featuredImage?.node} date={date} author={author?.node?.name} /> <Container> <ContentWrapper content={content} /> </Container> </> </Main> <PaginatedFooter post={props.data.post} /> </> );}Component.query = gql` ${BlogInfoFragment} ${NavigationMenu.fragments.entry} ${FeaturedImage.fragments.entry} query GetPost( $databaseId: ID! $headerLocation: MenuLocationEnum $footerLocation: MenuLocationEnum $asPreview: Boolean = false ) { post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { title content date author { node { name } } previousPost { title slug } nextPost { title slug } ...FeaturedImageFragment } generalSettings { ...BlogInfoFragment } headerMenuItems: menuItems(where: { location: $headerLocation }) { nodes { ...NavigationMenuItemFragment } } footerMenuItems: menuItems(where: { location: $footerLocation }) { nodes { ...NavigationMenuItemFragment } } }`;Component.variables = ({ databaseId }, ctx) => { return { databaseId, headerLocation: MENUS.PRIMARY_LOCATION, footerLocation: MENUS.FOOTER_LOCATION, asPreview: ctx?.asPreview, };};
There are 109 lines of code in this example so let’s break this down at a high level and then I shall focus on the changes I made to the actual boilerplate starter code with my own customization.
The templates in Faust as you see in the code block have 3 main parts: Component, Query, and Variable. Please refer to theFaust docs to get a deeper explanation of what each does.
Let’s start by focusing on the query layer of the template. At the bottom of the file, I have customized the query to ask for thepreviousPost
andnextPost
fields as shown:
Component.query = gql` ${BlogInfoFragment} ${NavigationMenu.fragments.entry} ${FeaturedImage.fragments.entry} query GetPost( $databaseId: ID! $headerLocation: MenuLocationEnum $footerLocation: MenuLocationEnum $asPreview: Boolean = false ) { post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { title content date author { node { name } } previousPost { title slug } nextPost { title slug }
The variables are already set to what I need, so the last thing I have to do is go back up to the top of the component which is the rendering layer and add thePaginatedFooter
component after importing it at the top within the return statement. Then I can pass in and destructure thepost props
data in thePaginatedFooter
component:
return ( <> <SEO title={siteTitle} description={siteDescription} imageUrl={featuredImage?.node?.sourceUrl} /> <Header title={siteTitle} description={siteDescription} /> <Main> <> <EntryHeader title={title} image={featuredImage?.node} date={date} author={author?.node?.name} /> <Container> <ContentWrapper content={content} /> </Container> </> </Main> <PaginatedFooter post={props.data.post} /> </> );
Super Stoked! First, runnpm run generate
for the types and then run this on the dev server. Let’s see this now work on the browser:
Conclusion 🚀
Faust.js and its new GraphQL client Apollo give developers a better way to develop headless WordPress.
I hope you have a better understanding of how to work with Apollo in Faust.js. As always, super stoked to hear your feedback and any questions you might have on headless WordPress. Hit us up in ourdiscord!
Top comments(1)

- LocationWheeling, WV 26003
- Joined
What is the benefit of Apollo client?diamond exch id
For further actions, you may consider blocking this person and/orreporting abuse