In part 1 of this series I taught you how to get up and running with WordPress and Docker. In this one we'll see how we can integrate React with WordPress' RESTful API to be used as a back-end solution for our CRUD project. Let's dive in.
Creating the ⚛️ React App
To create a new React single-page application, let's use create-react-app
, which is what's recommended in the official docs and is an opinionated, batteries included tool to quickly scaffold React applications. You can add it globally with the usual yarn global add create-react-app
or npm install -G create-react-app
, but I don't want it to stay on my computer since I will only use the command once per project, so I'll use npx
to execute the binary from a temporary location and then remove it.
npx create-react-app wordpress-react
Once we set up our project, we'll cd wordpress-react
and use yarn start
to get the live development server with Webpack rolling. I do not mean for this to be an introduction to React. The documentation and the countless tutorials available online will be more than enough to get you started. I'll show how to integrate the WP REST API with react-router
and I'll take this chance to make use of the new Context API of React 16.3+.
Let's add dependencies and get working on the app:
yarn add react-router react-router-dom
Project Structure
The first thing I always do to get my head around to working with a new project is setting up the directory structure. For this project we'll use the new Context API for state management and React Router v4 for routing, so knowing that we'll come up with the following structure to support our project:
- src
| - components
| - containers
| > Store.js
| > App.js
| > index.js
We'll use two types of components: presentational components, which will have all the styles and container components, which will handle interaction with Context via lifecycle methods and will pass those down to presentational components.
Let's clear up our App.js
:
import React from 'react';
const App = () => <div>Hello, WordPress!</div>;
export default App;
We'll revisit this one later when we add store and routes.
Extending Development Environment
Next up we'll add the final service to our docker-compose.yml
, which will tell Docker to add our front-end to the network. The npm run start
process will run inside a Docker container, but we'll mount our source directory as a volume, which will mean that any change we make during development will trigger the watcher inside the container and will reload the app just as if it were running on the host machine.
As of now, I am keeping everything inside the root project directory, that create-react-app
created for us. Let's add this new service to the docker-compose.yml
:
app:
depends_on:
- wordpress
build: .
ports:
- '3000:3000'
volumes:
- '.:/app'
You may notice the line
build: .
This tells compose to look for a Dockerfile
in the same directory, which we do not yet have. Let's fix that:
FROM node:alpine
RUN mkdir -p /app
COPY ./package.json /app/package.json
WORKDIR /app
RUN npm install
ENTRYPOINT [ "npm", "start" ]
We'll copy our package.json
for NPM to be able to install our dependencies, before the mounting happens. This will mean that whenever we restart the container, it will not have to pull dependencies every time. One more thing we need to add in order for the React App to reach the WordPress instance is the proxy
config field in the package.json
file, like so:
{
...
"proxy": "http://wordpress"
}
Let's see what will happen inside and outside the Docker network:
We can still access the WordPress service too, via visiting http://localhost
on our computers, however that is not how it can be accessed inside the network, which is the reason why we added it to a proxy config. This means that requests that should not be served with a valid URL within our application will be redirected to go to the WordPress service.
Setting Up Store
React 16.3 brought about a new Context API, which is more intuitive and in this guide it will replace Redux, which is what I initially wanted to use before the new stuff came out. The way the new API works is you have Provider
and Consumer
tied to each context you create. To get these two, you can fire off a call to the new createContext
, which accepts a parameter, which will be the default value that can be accessed inside the context. Using context is still a way to bypass intermediate components, when passing data deep down within components, however the new API is way more intuitive and is actually easier to set up than Redux, so let's see how it is done.
// Store.js
import React, { createContext, Component } from 'react';
const { Provider, Consumer } = createContext();
We could just wrap the whole application insde the <Provider/>
, however we also want to be able to retrieve data, which will manipulate context. For this we'll create a wrapper component, which uses the Provider and can pass down data as well as methods for data retrieval, much like mapDispatchToProps
in redux
.
class Store extends Component {
state = {
posts: [],
};
render() {
const {
state: { posts },
props: { children },
} = this;
return <Provider value={{ posts, loadPosts }}>{children}</Provider>;
}
}
We can test out how the <Consumer/>
works by just initializing the state with some sample data and creating the presentational components. Inside the containers
directory we'll create components that make use of the Consumer
's children
prop, which is a function that recieves whatever is the current value
of the Provider
.
Let's add this container component:
// containers/PostsContainer.js
// ... imports
const PostsContainer = () => (
<Consumer>{({ posts }) => <PostsView posts={posts} />}</Consumer>
);
export default PostsContainer;
We also need to create this component which will interact with the consumer via props. Now theoretically we could just create this in the same file, since we should not use this separately, however for testability, this approach is better.
// components/PostsView.js
import React, { Component } from 'react';
class PostsView extends Component {
render() {
const { posts } = this.props;
return posts.length ? (
posts.map(({ title, id, date, slug, excerpt }) => <p>{title.rendered}</p>)
) : (
<div>Loading...</div>
);
}
}
export default PostsView;
At this point we do not have any data coming in, so before we add actual server-side data, we'll just add the following dummy data to initialize the Store with:
// Store.js
class Store extends Component {
state = {
posts: [
{
id: 1,
title: { rendered: 'Hello, HardCoded Data!' },
date: '2018-04-17T00:17:18.040Z',
slug: 'hello-world',
excerpt: { rendered: 'Some random text from the excerpt.' },
},
],
};
// ...
}
The post object is the exact structure that is returned by the WordPress REST API. Finally let's add the container component and the store to the application itself.
// App.js
// ... imports
class App extends Component {
render() {
return (
<Store>
<PostsContainer />
</Store>
);
}
}
export default App;
Let's take a look at how to actually retrieve data:
// Store.js
class Store extends Component {
// ...
loadPosts = () => {
fetch('/wp-json/wp/v2/posts')
.then(res => res.json())
.then(posts => this.setState({ posts }));
};
render() {
const {
state: { posts },
props: { children },
loadPosts,
} = this;
return <Provider value={{ posts, loadPosts }}>{children}</Provider>;
}
}
Here we added a new method called loadPosts
, which makes a network call and upon recieving data, it will set the state to the new data, which in turn will also update each Consumer
. We also need to add the new method to the provider's values. This is what we'd do when we provide the mapDispatchToProps
parameter of redux-connect
. The final thing we have to do is make use of it in the PostsView
component.
// components/PostsView.js
class PostsView extends Component {
componentDidMount() {
this.props.loadPosts();
}
// ...
}
This is way simpler than doing it with thunks or redux-saga
, however we are still coupling all our logic in a single component, which is better than having it in the state of each individual component, but may get clunky with large applications.
In the next and final post of this series we'll see how we can integrate React Router with this application as well as adding a method to retrieve a single post. If you are already familiar with React Router, you'll know how to go on about creating the rest of the site using WordPress and React. Otherwise, be on the lookout for the next post.
Happy hacking! 😉
Top comments (0)