Dynamic React Routing With WordPress

Eric Stout
factor1
Published in
6 min readMar 15, 2018

With WordPress being one of the most popular CMS platforms on the internet and the rise of React front-ends, it’s only natural that we are starting to see more and more headless WordPress implementations. While we were exploring React/WordPress solutions over at Factor1, some of the biggest hurdles we had to clear was being able to dynamically set our routes (using React Router), match templates to components (with the ability to be changed later by the client), and overall maintain the WordPress flexibility in our React application. We pride ourselves on how easy our sites are to use, and we couldn’t give any of that up due to the fact we were introducing a new technology to the front-end.

Setting Up Your Environment

So let’s get going. First, install WordPress if you haven’t — and ensure we have some content in there to play with. This can be a local WordPress install on your machine or a remote install on a server, doesn’t matter, as we will be dropping the WordPress URL into a constant later.

WordPress API & Better Rest Endpoints

When using React with a headless WordPress backend, we will be hitting the Rest API often to get our data. If you’re familiar with the WordPress Rest API you know that the data can be pretty bloated. We saw a need for a better endpoint solution during our testing and thus Better Rest Endpoints was born. You can read more about the plugin over on this article, but we will be using it for our endpoints in this article instead of hitting the default WordPress Rest API.

Set Up React & React Router

For this article, we will use create-react-app to setup our React application. If you haven’t installed it yet, you can install it via the command line:

npm install create-react-app -g

Once you have create-react-app installed on your machine, navigate to where you want your project to live and run create-react-app router-fun, we will call this project “router-fun”. Then navigate into your project cd router-fun.

Next, we need to install React Router. We will switch to the package manager Yarn for packages inside our React app from here on out.

yarn add react-router-dom

Starting Your App

Now we are all set to get coding. By default create-react-app gives us a starting index.js, so let’s open that up. We can remove everything inside this file.

Let’s add all the imports we need to the top of our file:

import React from 'react';
import {render} from 'react-dom';
import {
BrowserRouter as Router,
Route,
Switch
} from 'react-router-dom';
// helper to check fetch status
import checkStatus from './etc/checkStatus';
// Some of our example components
import Page from './Components/Page';
import Home from './Components/Home';
import About from './Components/About';
import NotFound from './Components/NotFound';
import Header from './Components/Header';

You can see we imported some components as well. Obviously these would vary depending on your project but we need to know they exist.

Getting Our WordPress Pages

Next, we are going to write a function that returns a promise to get our pages so we can build the routes. This is where the Better Rest Endpoints plugin really ✨sparkles✨. If we were to use the standard WordPress Rest API each page that would get returned would include all of its data, including content. All we are looking for here from this response is the template that is set in WordPress, the id, parent, and slug. So with our endpoint we can exclude a lot of the data we don’t want. Take a look at the Better Rest Endpoint documentation for more information. Our endpoint ends up looking like this: ${siteURL}/wp-json/better-rest-endpoints/v1/pages?content=false&acf=false&media=false&per_page=99

Lets take a look at the whole function:

const siteURL = 'http://localhost:3000';const getPages = () => {
return new Promise((resolve, reject) => {
const endpoint = `${siteURL}/wp-json/better-rest-endpoints/v1/pages?content=false&acf=false&media=false&per_page=99`
fetch(endpoint)
.then(checkStatus)
.then(response => response.json())
.then(pages => {
resolve(pages);
})
.catch(error => console.error(error))
})
}

Templates & Components

Now that we have our function to get pages, we will need to handle the mapping of WordPress templates to our components when building our routes. So let’s write a function to accomplish that.

const identifyComponent = (page) => {
// this is where you map your template names to a component
const components = {
'home': Home,
'about': About
}
// check the template from the response
if(page.template !== 'default' && page.template){
return components[page.template];
} else {
return Page
}
}

All this does is use an object to map what page templates you have coming from WordPress to your React Components and checks the what component to use when building the routes.

App Initialization and Building Routes

Now, let’s create a class called AppInit to handle the initialization of our React App and to build our routes. This is a lot, but hang in there, we will break it down later.

class AppInit {  // build the app and routes with page response
buildApp() {
async function buildRoutes (){
let pageList = await getPages();
render(
<div>
<Router>
<div>
<Header />
<Switch>
<Route path="/" render={(props) => <Home siteURL={siteURL} {...props} />} exact />
{pageList.map( (page, i) => {
let Template = identifyComponent(page)
let pageID = page.id;
let parent = page.parent;
return(
<Route
key={pageID}
path={`${parent ? '/'+parent :''}/${page.slug}`}
render={(props) => <Template pageID={pageID} siteURL={siteURL} {...props} />}
exact
/>
)
})}
<Route component={NotFound} />
</Switch>
</div>
</Router>
</div>
, document.getElementById('root')
)
}
buildRoutes();
}
/*
* Run the App
*/
run() {
this.buildApp();
}
}new AppInit().run();

Whew. Ok. Let’s break it down a bit. As you can see, we are using async/await to ensure our page data loads before rendering. We also know that our homepage is going to use the Home component so we can go ahead and make that route ourselves, so no different then a normal React Router Route

<Route path="/" render={(props) => <Home siteURL={siteURL} {...props} />} exact />

Then, we are going to want to go through all our pages using .map() to establish our slugs and build our routes.

{pageList.map( (page) => {
// use the identifyComponent function we wrote earlier
let Template = identifyComponent(page)
let pageID = page.id;
let parent = page.parent;
return(
<Route
key={pageID}
// if we have a parent, put that in front of the slug
path={`${parent ? '/'+parent :''}/${page.slug}`}
render={(props) => <Template pageID={pageID} siteURL={siteURL} {...props} />}
exact
/>
)
})}

That should handle our routes, but we also would want to handle a potential 404:

<Route component={NotFound} />

Then we make sure that we add new AppInit().run(); to the end of our file to initialize our app.

Wrapping Up

All together, your index.js should look something like this:

import React from 'react';
import {render} from 'react-dom';
import {
BrowserRouter as Router,
Route,
Switch
} from 'react-router-dom';
// helper to check fetch status
import checkStatus from './etc/checkStatus';
// Some of our example components
import Page from './Components/Page';
import Home from './Components/Home';
import About from './Components/About';
import NotFound from './Components/NotFound';
import Header from './Components/Header';
const siteURL = 'http://localhost:3000';const getPages = () => {
return new Promise((resolve, reject) => {
const endpoint = `${siteURL}/wp-json/better-rest-endpoints/v1/pages?content=false&acf=false&media=false&per_page=99`
fetch(endpoint)
.then(checkStatus)
.then(response => response.json())
.then(pages => {
resolve(pages);
})
.catch(error => console.error(error))
})
}
const identifyComponent = (page) => {
// this is where you map your template names to a component
const components = {
'home': Home,
'about': About
}
// check the template from the response
if(page.template !== 'default' && page.template){
return components[page.template];
} else {
return Page
}
}
class AppInit {// build the app and routes with page response
buildApp() {
async function buildRoutes (){
let pageList = await getPages();
render(
<div>
<Router>
<div>
<Header />
<Switch>
<Route path="/" render={(props) => <Home siteURL={siteURL} {...props} />} exact />
{pageList.map( (page, i) => {
let Template = identifyComponent(page)
let pageID = page.id;
let parent = page.parent;
return(
<Route
key={pageID}
path={`${parent ? '/'+parent :''}/${page.slug}`}
render={(props) => <Template pageID={pageID} siteURL={siteURL} {...props} />}
exact
/>
)
})}
<Route component={NotFound} />
</Switch>
</div>
</Router>
</div>
, document.getElementById('root')
)
}
buildRoutes();
}
/*
* Run the App
*/
run() {
this.buildApp();
}
}new AppInit().run();

Now you should have a React app that pulls from the WordPress Rest API that dynamically creates the routes you need. In this article, we didn’t cover working with posts, but it can easily be achieved using React Router. If you are using the WordPress Rest API be sure to check out Better Rest Endpoints which can give you easier access to WordPress Menus, Custom Post Types, Advanced Custom Fields, and more.

--

--

Eric Stout
factor1
Editor for

software engineer — baseball fanatic — video game nerd