In this tutorial, you will learn how to create a simple headless WordPress app using React and WPGraphQL. This tutorial assumes a basic understanding of JavaScript, React fundamentals, GraphQL fundamentals, and WordPress. Using the prepared Code Sandbox repository will allow us to focus on points specific to React as a framework and Apollo Client for data fetching. By the end of this tutorial, you will be able to:
- Use the GraphiQL IDE to compose GraphQL queries
- Use Apollo Client to fetch data in your app and render it to the page
- Use variables in GraphQL queries
Code Sandbox Overview
First, letās open up the Code Sandbox that we have prepared for this project. Hereās the link!
If you havenāt used Code Sandbox previously, there are four sections on the screen. On the left, you can see the file structure of your project. Below are the dependencies that weāve already added to this project (@apollo/client
& graphql
). You can make changes to the files using the code editor in the middle and see your changes reflected in real-time in the embedded browser on the right.
![](https://wpengine.com/builders/wp-content/uploads/2022/04/Screen-Shot-2022-04-19-at-5.13.04-PM-1024x530.png)
If at any point you encounter an unexpected error after you make changes, you can refresh the embedded browser, or even refresh the whole Code Sandbox page to get things working again. CodeSandbox will have saved your changes.
React App Overview
Before we start coding, letās take a look at the current state of this Code Sandbox application. In the embedded browser, it is displaying a page titled Blog
that consists of many cards. Each card contains data for a blog post, including the postās title, date, author, and featured image, if it has one.
![](https://wpengine.com/builders/wp-content/uploads/2022/04/Screen-Shot-2022-04-19-at-5.15.34-PM-902x1024.png)
We can also click on each of these cards to view an individual post details page. This page includes much of the same information as on the card, and also contains the postās content. In the address bar of the embedded browser, we can also see that the URL changes to reflect the current page.
![](https://wpengine.com/builders/wp-content/uploads/2022/04/Screen-Shot-2022-04-19-at-5.16.17-PM-897x1024.png)
How are we making this happen? Open the App.js
file in the Code Sandbox project. In this file, we are first importing React
and some packages from react-router-dom
to handle our routing. Then, weāre importing two files – HomePage
and PostPage
– as well as some styles for our app.
Inside of the <Switch>
tag, the two <Route>
tags are managing the routing. They check the path from the browser and render a certain component based on that path. As you change the URL while navigating the site, the component being displayed changes. This forms the basis of our navigation between the HomePage
showing the list of all posts and the PostPage
showing the individual postās details.
Now, letās dive deeper into the HomePage
. /pages/HomePage.js
looks like this:
export default function HomePage() {
return (
<div className="page-container">
<h1>Blog</h1>
<PostsList />
</div>
);
}
Code language: JavaScript (javascript)
HomePage
renders a component called PostsList
, which lives in /components/PostsList.js
and looks like this:
import React from "react";
import PostCard from "../components/PostCard";
import { data } from "../dummy-data/posts";
export default function PostsList() {
const loading = false;
const error = null;
if (loading) return <p>Loading posts...</p>;
if (error) return <p>Error :(</p>;
const postsFound = Boolean(data?.posts.nodes.length);
if (!postsFound) {
return <p>No matching posts found.</p>;
}
return (
<div className="posts-list">
{data.posts.nodes.map((post) => (
<PostCard key={post.databaseId} post={post} />
))}
</div>
);
}
Code language: JavaScript (javascript)
This is where the actual data fetching occurs to display the list of all posts on the HomePage
. However, we are currently importing hard-coded JSON dummy data from the /dummy-data/posts.js
file to populate this page. Your challenge is to remove this dummy data and fetch REAL data from a headless WordPress backend. This is where WPGraphQL and Apollo Client come in!
Create an Apollo Client Instance
Letās get started with setting up our React app to fetch data from a WordPress backend! A number of GraphQL clients exist that you could use for this purpose. For our app, we’re going to use Apollo Client. Per Apollo Client’s Get Started documentation, we first need to create an instance of Apollo Client.
Inside the src directory, create a new folder called lib
. Inside lib
, create a file named apollo.js
. Inside the newly created /lib/apollo.js
file, add the following code:
import { ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
uri: ' ',
cache: new InMemoryCache(),
});
export default client;
Code language: JavaScript (javascript)
The first line of code here imports ApolloClient
and InMemoryCache
from the @apollo/client
package. Then, below that, we create a new instance of ApolloClient
and export that as a variable named client
. When we create the new client, we pass in a configuration object with uri
and cache
properties.
The cache
property gets assigned to a new InMemoryCache
instance. With this in place, the cache will “remember” the results of the GraphQL queries that get executed. If the same queries are requested again, Apollo Client will resolve the requests using the data in its cache rather than making a new network request every time. Read more about Apollo Client’s caching capabilities here.
The uri
property needs to point to the GraphQL endpoint we want to source our data from. The endpoint that we will be using for this tutorial is https://content.wpgraphql.com/graphql. If you already have a WordPress server with an instance of WPGraphQL installed, you can use that instead, if you’d like. After adding this value to the uri
, our file will look like this:
import { ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
uri: 'https://content.wpgraphql.com/graphql',
cache: new InMemoryCache(),
});
export default client;
Code language: JavaScript (javascript)
With our client fully configured, the next step is to make it available anywhere in our application using the ApolloProvider
component.
Implement Apollo Provider Component
Apollo Client provides an ApolloProvider
component that can be used to add the ApolloClient instance we created to React context. This will allow us to run GraphQL queries from anywhere within our app’s component tree.
Inside of App.js
, add the following import statements. These pull in the client
we created in the previous step and the ApolloProvider
component that Apollo Client provides. We will need both of these to make a connection.
import { ApolloProvider } from "@apollo/client/react";
import client from "./lib/apollo";
Code language: JavaScript (javascript)
Next, wrap the <Switch>
component inside of an <ApolloProvider>
component, passing the configured client
as a prop:
export default function App() {
return (
<ApolloProvider client={client}>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/blog/:slug" component={PostPage} />
</Switch>
</ApolloProvider>
);
}
Code language: JavaScript (javascript)
Wrapping your app in an ApolloProvider
gives you the ability to use the React hooks that Apollo Client provides, such as useQuery()
, from anywhere in your app.
Import useQuery()
Hook in PostsList
Now that we have a fully configured GraphQL client, we can start fetching some real data inside of our components. As mentioned previously, the HomePage
component inside /pages/HomePage.js
renders the PostsList
component to display the list of blog posts. Open up /components/PostsList.js
in the editor so we can take a look at the PostsList component.
export default function PostsList() {
const loading = false;
const error = null;
if (loading) return <p>Loading postsā¦</p>;
if (error) return <p>Error :(</p>;
const postsFound = Boolean(data?.posts.nodes.length);
if (!postsFound) {
return <p>No matching posts found.</p>;
}
return (
<div className="posts-list">
{data.posts.nodes.map((post) => (
<PostCard key={post.databaseId} post={post} />
))}
</div>
);
}
Code language: JavaScript (javascript)
This PostsList
component first includes checks for whether the data is loading, returned an error, or is empty. If none of these conditions are true, then the code map
s over the array of data, passing rendering a PostCard
component for each.
For now, this component is pulling in placeholder dummy data. Let’s work toward populating it with dynamic data. First, letās import two things – gql
and useQuery
– at the top of our /components/PostsList.js
file:
import { gql, useQuery } from "@apollo/client";
Code language: JavaScript (javascript)
So now we’re set up to fetch data, but what does our GraphQL query need to look like? š¤ Let’s learn how to compose the GraphQL query we need using the GraphiQL IDE, next.
Compose GraphQL Query Using the GraphiQL IDE
To compose our GraphQL query, we’ll use a hosted instance of the GraphiQL IDE that’s available at graphiql-online.com. (Note the “i” in “GraphiQL”! It can easily be mistaken for the word “GraphQL” at first glance š).
Once you’ve opened graphiql-online.com in a new browser tab, enter https://content.wpgraphql.com/graphql
for the GraphQL endpoint and click the button to continue. Recall that this is the same endpoint that we configured Apollo Client to use inside of /lib/apollo.js
.
All of the data that exists in the GraphQL schema is shown in the Explorer
section on the left. Developers can select boxes to indicate which data they would like to fetch, then GraphQL builds the query for them! Give it a try! Once you’ve checked a few boxes and composed a query, hit the play icon (ā¶ļø) to execute it and see the data that gets returned in the right-most pane.
By looking at the data inside of /dummy-data/posts.js
, we can see the shape of the data that our PostsList
component expects. Using the GraphiQL Explorer, try to compose a query that looks like this:
![](https://wpengine.com/builders/wp-content/uploads/2022/04/Screen-Shot-2022-04-20-at-10.02.26-AM-1024x461.png)
Once you compose a query that’s returning the correct data, we can copy/paste that query into our app.
Format Query with gql
Back in the PostsList
component, add this code underneath the import
statements. Here, we pass the query we composed to a gql
tagged template literal (āgqlā followed by two backticks) and assign the return value to a GET_ALL_POSTS
variable.
const GET_ALL_POSTS = gql`
query getAllPosts {
posts {
nodes {
databaseId
title
date
slug
author {
node {
name
}
}
featuredImage {
node {
altText
sourceUrl
}
}
}
}
}
`;
Code language: JavaScript (javascript)
gql
takes care of converting the query string into an abstract syntax tree (AST) object that Apollo Client can work with.
Fetch Data with the useQuery()
Hook
Now let’s actually execute our GraphQL query. First, delete the dummy data import statement:
import { data } from "../dummy-data/posts";
Code language: JavaScript (javascript)
…and also delete these variables from inside of the export
statement in the PostsList
component:
const loading = false;
const error = null;
Code language: JavaScript (javascript)
We will replace those variables with the destructed response that we get from useQuery()
. Passing the GET_ALL_POSTS
function into useQuery()
will result in values for variables named loading
, error
, and data
:
const { loading, error, data } = useQuery(GET_ALL_POSTS);
Code language: JavaScript (javascript)
Finally, our HomePage
is dynamic! You should now be able to hit the refresh button on the embedded browser and see the “Loading posts…” message momentarily, then see the grid of posts appear.
Congratulations! You are now successfully pulling data from a headless WordPress backend and rendering it out inside of a React app! š
The final PostsList.js
file should look like this:
import React from "react";
import PostCard from "../components/PostCard";
import { gql, useQuery } from "@apollo/client";
const GET_ALL_POSTS = gql`
query getAllPosts {
posts {
nodes {
databaseId
title
date
slug
author {
node {
name
}
}
featuredImage {
node {
altText
sourceUrl
}
}
}
}
}
`;
export default function PostsList() {
const { loading, error, data } = useQuery(GET_ALL_POSTS);
if (loading) return <p>Loading postsā¦</p>;
if (error) return <p>Error :(</p>;
const postsFound = Boolean(data?.posts.nodes.length);
if (!postsFound) {
return <p>No matching posts found.</p>;
}
return (
<div className="posts-list">
{data.posts.nodes.map((post) => (
<PostCard key={post.databaseId} post={post} />
))}
</div>
);
}
Code language: JavaScript (javascript)
In the next section, weāll look at making similar changes to the PostPage
file.
Making PostPage.js
Dynamic
In the last section, we focused on pulling multiple posts into the site home page using Apollo Client and WPGraphQL. This section will build on that example and look at how to create individual pages for each blog post, each with a unique URL.
To begin, letās open the /pages/PostPage.js
file and look at the existing code. It currently imports dummy data from /dummy-data/post.js
and passes it into the PostPageContent
component.
import React from "react";
import { Link } from "react-router-dom";
import PostPageContent from "../components/PostPageContent";
import { data } from "../dummy-data/post";
export default function PostPage(props) {
const loading = false;
const error = null;
const postFound = true;
return (
<div className="page-container">
<Link to="/">ā Home</Link>
{loading ? (
<p>Loading...</p>
) : error ? (
<p>Error: {error.message}</p>
) : !postFound ? (
<p>Post could not be found.</p>
) : (
<PostPageContent post={data.post} />
)}
</div>
);
}
Code language: JavaScript (javascript)
As we did once before, let’s remove the dummy data and do some real data fetching instead.
Construct GET_POST_BY_SLUG
Query
Back in the GraphiQL IDE, use the Explorer once more and select post
from the list of schema objects. If you check the id
and idType
options, GraphiQL will help you begin to parameterize your query so that we can pass in variables. First, we can set the idType
to SLUG
from the dropdown in the Explorer. We want to use a dynamic variable for the slug so it can change every time we hit a new route. To accomplish this, we’ll have the query accept a $id
variable with a required type of ID
, like this: getPostBySlug($id: ID!)
. Next, we’ll pass that value in as the id
, like this: post( id: $id, idType: SLUG )
. This tells GraphQL to use the value of $id
to locate the blog post.
Your query should then look like the one shown below. You can once again hit the play icon (ā¶ļø) to execute it and see the data that gets returned in the right-most pane.
![](https://wpengine.com/builders/wp-content/uploads/2022/04/Screen-Shot-2022-04-20-at-11.06.32-AM-1024x401.png)
As you can see, this query returned an error because there was no id
value specified. We can fix this by passing in the slug from one of the posts that our getAllPosts
query returned previously in the Query Variables
pane at the bottom of the screen, like this:
{
"id": "query-any-page-by-its-path-using-wpgraphql"
}
Code language: JSON / JSON with Comments (json)
Now, if we press the play icon (ā¶ļø) again, the query should return the corresponding post details.
![](https://wpengine.com/builders/wp-content/uploads/2022/04/Screen-Shot-2022-04-20-at-11.02.59-AM-1024x400.png)
Once you verify that the query is correct, we will copy/paste the query that you composed into your app.
Call useQuery()
Hook from PostPage.js
Now let’s execute our GraphQL query. Back in the /pages/PostPage.js
file, delete the dummy data import statement:
import { data } from "../dummy-data/post";
Code language: JavaScript (javascript)
Then, letās import two things: gql
and useQuery
:
import { gql, useQuery } from "@apollo/client";
Code language: JavaScript (javascript)
Next, add this code underneath the import
statements. Here, we once again will pass the query we composed to gql
and assign the return value to a GET_POST_BY_SLUG
variable.
const GET_POST_BY_SLUG = gql`
query getPostBySlug($id: ID!) {
post(id: $id, idType: SLUG) {
title
date
content
categories {
nodes {
slug
name
}
}
author {
node {
name
}
}
}
}
`;
Code language: PHP (php)
Now that our query is set up in the application, we can use it throughout the rest of this page component.
First, inside of the export
statement in the PostPage
component, delete these two lines:
const loading = false;
const error = null;
Code language: JavaScript (javascript)
We will replace those variables with the destructed response that we get from useQuery()
. Passing the GET_POST_BY_SLUG
function into useQuery()
will result in values for variables named loading
, error
, and data
:
const { loading, error, data } = useQuery(GET_POST_BY_SLUG);
Code language: JavaScript (javascript)
However, we need to pass the slug into our useQuery()
hook as a parameter. We can access the slug through the props
value that is passed into the PostPage
component at props.match.params.slug
and send it through useQuery()
as a specifically-formatted object. We can edit the code from the previous step to include this:
const { loading, error, data } = useQuery(GET_POST_BY_SLUG, {
variables: {
id: props.match.params.slug
}
});
Code language: JavaScript (javascript)
Lastly, we also need to replace the postFound
variable with a Boolean
value indicating whether or not there is a post
present inside of data
.
const postFound = Boolean(data?.post);
Code language: JavaScript (javascript)
After making these changes, our final /pages/PostsPage.js
file should look like this:
import React from "react";
import { Link } from "react-router-dom";
import PostPageContent from "../components/PostPageContent";
import { gql, useQuery } from "@apollo/client";
const GET_POST_BY_SLUG = gql`
query getPostBySlug($id: ID!) {
post(id: $id, idType: SLUG) {
title
date
content
categories {
nodes {
slug
name
}
}
author {
node {
name
}
}
}
}
`;
export default function PostPage(props) {
const { loading, error, data } = useQuery(GET_POST_BY_SLUG, {
variables: {
id: props.match.params.slug
}
});
const postFound = Boolean(data?.post);
return (
<div className="page-container">
<Link to="/">ā Home</Link>
{loading ? (
<p>Loadingā¦</p>
) : error ? (
<p>Error: {error.message}</p>
) : !postFound ? (
<p>Post could not be found.</p>
) : (
<PostPageContent post={data.post} />
)}
</div>
);
}
Code language: JavaScript (javascript)
Done! š
You should now be able to navigate back and forth between the homepage, which shows the list of posts, and the single post pages.
Congratulations on creating a headless WordPress site! Thanks to your hard work, the app is now leveraging Apollo Client and WPGraphQL to pull data from a real headless WordPress backend and use it to render your pages!
We covered a lot of ground in this crash course. Hopefully, you now have a good understanding of how you can leverage tools like React and WPGraphQL to build headless WordPress sites.
Thanks for following along!