Crash Course: Build a Simple Headless WordPress App with React & WPGraphQL

14 minute read
Grace Erixon
Grace Erixon
Associate Developer Advocate

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.

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.

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.

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>
  );
}

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>
  );
}

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;

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;

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";

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>
  );
 }

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>
 );
}

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 maps 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";

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:

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
        slug
        author {
          node {
            name
          }
        }
        featuredImage {
          node {
            altText
            sourceUrl
          }
        }
      }
    }
  }
`;

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";

…and also delete these variables from inside of the export statement in the PostsList component:

const loading = false;
const error = null;

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);

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
        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>
 );
}

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>
  );
}

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.

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"
}

Now, if we press the play icon (▶️) again, the query should return the corresponding post details.

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";

Then, let’s import two things: gql and useQuery:

import { gql, useQuery } from "@apollo/client";

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
        }
      }
    }
  }
`;

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;

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);

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: {
		slug: props.match.params.slug
	}
}

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);

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: {
		slug: 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>
 );
}

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!