In my last post, I discussed the general tech stack that I’ve been using lately for Headless WordPress builds. One of the main components of this stack is Repress.
Repress is a small library written by Ryan McCue from Human Made that works with Redux and allows you to access your WordPress REST API data easily and with minimum fuss. It contains a number of higher-order components (HOC) that reduce boilerplate code as well as enabling you to use the library manually for more complex tasks when needed.
Installing Repress
Installing Repress is pretty simple, it’s an NPM package that you can install using npm
or yarn
.
# For npm run
npm install --save @humanmade/repress
# For Yarn run
yarn add @humanmade/repress
Code language: PHP (php)
Once you’ve installed the package, you’ll need to get it set up your handlers, this sets up the reducers and default actions for a post type (If I’ve not mentioned it before, Repress works via post type).
The official documentation for this, says to do it in a single file called types.js
. For the first project I used Repress on, I followed this advice. Unfortunately, I found that the file became pretty large and confusing and as such, I now split out each post type’s handler into a separate file, E.G. posts.js
, pages.js
, users.js
etc.
Setting handlers up is pretty simple, you import handler
from the @humanmade/repress
package and then use it as follows:
// posts.js
import { handler } from '@humanmade/repress';
export const posts = new handler( {
// `type` (required): used to derive the action names, and typically should
// match the object type (post type, taxonomy, etc).
type: 'posts',
// `url` (required): base URL for the type.
// This will point to the WP API endpoint for this post type.
url: `${process.env.REACT_APP_WP_API_BASE}/wp/v2/posts`,
} );
Code language: JavaScript (javascript)
You may have noticed a strange piece of code in there with the url. This piece of code ${process.env.REACT_APP_WP_API_BASE}
is actually pretty cool as it allows you to set an environment variable in your .env
file called WP_API_BASE
and it’ll pull the value from there. This way, you don’t have to change the URL for each environment and can simply have a different .env
file.
The URL, however you use it, will need to point to the WordPress REST API endpoint for the post type you’re setting up. In this case, its’ posts, so the URL will point to something like https://example.com/wp-json/wp/v2/posts
. For pages, it would be https://example.com/wp-json/wp/v2/pages
, you get the picture.
Anyway, once you’ve set up your handler, you’ll need to import it as a reducer into Redux. This section is very open and it’ll completely depend on your project setup, but somewhere you should have a combineReducers()
call. At this point, you’ll want to import your handler from its file and add it to the reducer. Something like this:
// reducer.js
import { combineReducers } from 'redux';
// Importing from posts.js
import { posts } from './posts';
export default combineReducers( {
// Any regular reducers you have go in here just like normal.
// Then, create a substate for your handlers.
posts: posts.reducer,
} );
Code language: JavaScript (javascript)
At this point, your handler is now all connected up and you can repeat these steps for any other post types you may want to work with.
Retrieving single posts
To retrieve a single post using our new handler, we can use one of the HOCs that comes with Repress. This one is aptly named withSingle()
. We’re going to continue on the same track and use posts as our example. I know React has gone over to functional components and they’re all the rage now, but I like ES6 classes and as such these examples will use them. Feel free to convert them over if you like though. /moan
.
This is how I like to use Repress with a React component.
// SinglePost.js
import { withSingle } from '@humanmade/repress';
import React from 'react';
import { posts } from './posts';
class SinglePost extends React.Component {
render() {
// Get the post and loading props out of this.props.
const { post, loading } = this.props;
// If the post is loading, show a loading indicator.
if(loading) {
return 'Loading...'
}
// If the post isn't loading, display the post.
return (
<div>
<h1>{post.title.rendered}</h1>
</div>
)
}
}
// Export the component using the withSingle HOC.
export default withSingle(
// Handler object:
posts,
// getSubstate() - returns the substate
state => state.posts,
// mapPropsToId - resolve the props to the post ID
props => props.id
)(SinglePost);
Code language: JavaScript (javascript)
It might look like there’s a lot going on here, but there really isn’t, so let’s walk through it. Firstly we’re importing the packages we’re going to use. React
for our component class, withSingle
is our HOC that connects this component to our handler and posts
from our posts.js
file we created earlier, as we’re going to use this in the HOC.
We then define our React component as usual, the only difference is that the HOC supplies some props of its own which we’re pulling out with the line: const { post, loading } = this.props;
. These props are pretty self-explanatory, loading
is a boolean value and lets us know whether Repress is currently fetching the post we’re trying to display. Whilst post
is the result from the API as a JavaScript object. How easy is that?
The bit you may find slightly confusing is the HOC export, but it’s really not that hard. Lets walk through it: export default withSingle()(SinglePost)
this is what actually links the HOC to our component. The withSingle()
HOC then expects three arguments.
- The handler that should be used to fetch the data from the API. In this case, it’s
posts
fromposts.js
. That’s why we needed to import it at the start. - The second is an odd
getSubstate()
function and this is what took me the longest to get my head around with Repress. In this example, it’s written asstate => state.posts
but it could quite easily be written likefunction( state ){ return state.posts }
. It’s basically a function that receives the current Redux state and needs to return the location of the substate for the resource we want. In our example, this isstate.posts
but it’s generally the object key that the handler was registered under when you calledcombineReducers()
earlier. E.G. for pages, it may look likestate => state.pages
. - This is where you’re getting the ID for the post from and is another shorthand function like the last parameter. This isn’t so confusing though and I find it’s easiest to stick with this as the default. This default just says, look at the
props.id
prop to get the post ID.
If you use the default for #3 above, you can then call your component like this <SinglePost id="3" />
and this will then fetch and render the post with the ID of 3. Can this get any easier?
Post type archives
Funnily enough, archives are also pretty simple in Repress and the work we did for the single posts, has set up most of what we need for the archives. The only difference is that we need to register our archives separately to the handler, but this isn’t a hard thing to do. Let’s head back over to our posts.js
file and see what we need to do.
As you recall, our posts.js
file looks something like the below:
// posts.js
import { handler } from '@humanmade/repress';
export const posts = new handler( {
// `type` (required): used to derive the action names, and typically should
// match the object type (post type, taxonomy, etc).
type: 'posts',
// `url` (required): base URL for the type.
// This will point to the WP API endpoint for this post type.
url: `${process.env.REACT_APP_WP_API_BASE}/wp/v2/posts`,
} );
Code language: JavaScript (javascript)
This is all good for the single posts, but let’s look at what we need to do for archives.
Firstly, Repress’s handler
class, ships with a very useful method, called registerArchive()
. The registerArchive()
method accepts two arguments, the first is the name of the archive as you want to save it (any scalar value will work here) and the second is the query that you’d like to register to the archive. For example, if we’re registering an archive to pull all sticky posts out, you may use something like this:
posts.registerArchive( 'stickied', {
per_page: 3,
sticky: '1',
} );
Code language: JavaScript (javascript)
In this example, we’re registering an archive called stickied and passing through a query of sticky=1
and per_page=3
. The sticky=1
parameter, tells the API to only supply posts that are marked as sticky. Whilst the per_page=3
parameter, as you may have guessed, tells the API to give us three posts per page. This is very useful for pagination, which I’ll cover in a bit.
The query department is fairly straightforward and you can use a lot of query parameters that you might need to use. For a full list of parameters that you can use, see the official docs. You may, as I did, run into the need to use some parameters that aren’t available by default, E.G. if you want to query meta data or something more complex. In this case, you can install and activate the WP REST Filter plugin, which enables you to use all the public query vars of WP_Query to filter your API request with. For example, we can now do something like this:
posts.registerArchive( 'key_resources', {
per_page: 6,
filter: {
meta_key: 'key_resource',
meta_value: 1
},
} );
Code language: JavaScript (javascript)
In this example, we’ve asked for 6 items per page (the per_page
parameter). We’ve then got the new filter
parameter, that the WP REST Filter plugin enabled us to use. Within this filter
param, we’re passing through a meta query, in this case, we’re asking for any posts with the meta key of key_resource and the meta value of 1. This is an actual example of one I’ve used in a project and the meta_key
actually relates to an Advanced Custom Fields field (if that’s of any help to you).
Now we’ve covered registering archives, lets look at how we consume them, once again using the HOC. This time, we’ll be using the withArchive()
HOC. A lot like withSingle()
, withArchive()
takes three arguments:
- The handler object, as per
withSingle()
, we’ll be usingposts
fromposts.js
. - Again, the second argument is the same as the second argument in
withSingle()
. It’s a function that returns the specific sub-state for the post type we’re calling. In this case, it will look like this:state => state.posts
. - This is where
withArchive()
differs slightly, instead of passing through a post ID, we’ll pass a string containing the archive ID that we gave it when we registered it. For example, we registeredstickied
andkey_resources
, in our example, we’ll use thekey_resources
archive.
Using the withArchive()
HOC is pretty much the same as using the withSingle()
version, it’s just the render function and props will look slightly different.
// PostArchive.js
import { withArchive } from '@humanmade/repress';
import React from 'react';
import { posts } from './posts';
class PostArchive extends React.Component {
render() {
// Get the posts and loading props out of this.props.
const { posts, loading } = this.props;
// If the archive is loading, show a loading indicator.
if(loading) {
return 'Loading...'
}
// If the archive isn't loading, loop over the posts and display them.
return (
<ul>
{ props.posts.map( post =>
<li key={ post.id }>
{ post.title.rendered }
</li>
) }
</ul>
)
}
}
// Export the component using the withSingle HOC.
export default withArchive(
// Handler object:
posts,
// getSubstate() - returns the substate
state => state.posts,
// Archive ID
'key_resources'
)(PostArchive);
Code language: JavaScript (javascript)
Let’s walk through this code then:
At the top, we have our imports, we’re importing the withArchive
HOC from Repress, React
for our class and posts
so that we can use it later in our HOC.
We then define our ES6 class and start our render()
function. Inside this, we’ll destructure the properties that we need from this.props
. In this case, it’s posts
that will contain an array of posts from the archive and loading
which once again will give us a boolean value to tell us whether the archive is loading or not.
Archive pagination
So far, we’ve set up a single post and a basic post archive, but what happens if we want to offer pagination or a “Load more” option to the user? Luckily for us, Repress also makes that easy.
We can build upon our current archive page to do this and it really only requires destructuring a few more properties, ready for it?
// PostArchive.js
import { withArchive } from '@humanmade/repress';
import React from 'react';
import { posts } from './posts';
class PostArchive extends React.Component {
render() {
// Get the posts and loading props out of this.props.
// Also now getting hasMore and loadingMore
const { posts, loading, hasMore, loadingMore } = this.props;
// If the archive is loading, show a loading indicator.
if(loading) {
return 'Loading...'
}
// If the archive isn't loading, loop over the posts and display them.
return (
<div>
<ul>
{ props.posts.map( post =>
<li key={ post.id }>
{ post.title.rendered }
</li>
) }
</ul>
{ loadingMore ?
<p>Loading more...</p>
: hasMore ?
<button
type="button"
onClick={ () => this.props.onLoadMore() }
>Load More</button>
: null }
</div>
)
}
}
// Export the component using the withSingle HOC.
export default withArchive(
// Handler object:
posts,
// getSubstate() - returns the substate
state => state.posts,
// Archive ID
'key_resources'
)(PostArchive);
Code language: JavaScript (javascript)
Let’s go through those new bits:
We’re now destructuring two more properties from this.props
. We’re getting the hasMore
property, which is a boolean value letting us know if there are more posts to be retrieved in this archive. We’re also getting the loadingMore
property, again this is a boolean value, but this time it tells us whether we’re in the process of fetching more posts from the archive.
The only other new bit of the class is within the render function and looks like this:
{ loadingMore ?
<p>Loading more...</p>
: hasMore ?
<button
type="button"
onClick={ () => this.props.onLoadMore() }
>Load More</button>
: null }
Code language: JavaScript (javascript)
This is actually just a few shorthand if statements put together. We first check to see if we’re loading more posts using the loadingMore
variable, if we are, we show Loading more… If we’re not loading more, we check to see if we have more posts, using the hasMore
variable. If there are no more posts, we return null
which in React will render nothing. If we do have more posts, we show this button:
<button
type="button"
onClick={ () => this.props.onLoadMore() }
>
Load More
</button>
Code language: HTML, XML (xml)
Hopefully, you can now see what this is doing. We’re outputting a button with the text Load More. This button has an onClick
handler that calls an arrow function, which in turn calls a Repress method from the withArchive()
HOC, this method is onLoadMore()
. We’re accessing it via this.props.onLoadMore()
but we could have also destructured it and called it as a variable.
The onLoadMore()
method, handles a lot in the background for you.
- Making the API call for more posts from this archive
- Updating the Redux store with the new posts
- Updates the internal Repress store to keep track of what page you’re on
There you have it, simple Load more style pagination. If you think you can do this in less code, then please show me how!
As a note, you can also do manual pagination pretty easily in Repress, but I’ll refer you to the official documentation for this as it’s not too much different than using the load more style.
What’s next?
I have quite a lot to say about Repress, I’ve used it a fair amount recently in both big and small projects. In my next post, I’m hoping to cover a little more about dynamic archives in Repress and how I’m using them.
If there’s anything else people would like to know, feel free to reach out to me on Twitter at @enshrined!