Creating a mobile app with WordPress REST API and React Native

Before I begin, it’s important to note that I’ve pulled a large portion of this post from @jrgould and his article from 2015 about the same topic, but I’ve updated the code to conform to modern React Native syntax and edited the project a bit differently.

In the following, we’ll set up a WordPress website with some content which we’ll use as a datasource for our React Native mobile app. With the advent of WordPress 4.7, we now have the ability to connect to any WordPress install through a preexisting REST API. This allows us to use WordPress as a backend so that we can build things like React Native apps that pull content from our WordPress site.

What we’re building

We’re going to build a React Native app called “Leadership-Quotes” that pulls images from a WordPress media library via the WP REST API and display a random leadership quote with each button click.

Getting started

Let’s set up a WordPress website first. To do this, you can install WordPress locally, or create a WordPress website with your hosting provider. I chose to set up a WordPress website with Pressable, and created a basic subdomain for the purpose of this project: https://leadershipquotes.mystagingwebsite.com. If you visit that site, you’ll notice there’s practically no content. This is because our React Native app will be pulling the content directly from the media library instead. If you need some content to upload to your media library, download my export and import it into your WordPress install.

Once you have your WordPress website set up, you can verify the REST API is working by visiting the URL: [your-domain.com]/wp-json/wp/v2/media. If you want to make sure it looks right, you can view the API for my site at https://leadershipquotes.mystagingwebsite.com/wp-json/wp/v2/media.

If you see something like that, and recognize some of the file names listed, then you’re good to go. That’s about it on the WordPress side for now.

Setting up a React Native project

The WP REST API allows for so much innovation with WordPress. Anyone can build whatever they’d like using whatever software they want and use WordPress as the backend. We’re going to use React Native. To keep this brief, brush up on your React Native skills with their getting started guide.

Following the getting started guide bootstraps our project with Create React Native App automatically. And if you’ve successfully followed their tutorial, you should also have Expo installed and ready to go. If not, grab your mobile device and install the Expo client app from your app library. You’ll be using Expo on your mobile device to test your app as you make changes.

In the Terminal on Mac, go to the location where you want this app installed for development, and run:

create-react-native-app Leadership-Quotes

cd Leadership-Quotes
npm start

After this, you should see a QR code in your Terminal window. Now open up Expo on your mobile device and click the “Scan QR Code” option, and scan the code. Your phone should refresh to show something like this.

Let’s begin editing the files. Open the App.js file in your favorite text editor. This file has some default code which displays the screen above. Let’s take a look at it.

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default class App extends React.Component {
  render() {
    return (
      < View style={styles.container}>
        < Text>Open up App.js to start working on your app!
        < Text>Changes you make will automatically reload.
        < Text>Shake your phone to open the developer menu.
      < /View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

The two import lines extend the React codebase and get it ready for what we need. Let’s add a few more items to that second import.

import { Text, View, StyleSheet, Dimensions, TouchableHighlight, Image } from 'react-native';

Let’s delete the rest of the default code and then add our own below those import tags.

// WP REST API 
const REQUEST_URL  = 'https://leadershipquotes.mystagingwebsite.com/wp-json/wp/v2/media';

// Windowsize is referenced in the styles below.
const windowSize = Dimensions.get('window');

export default class LeadershipCards extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isLoading: true
    }
    this.fetchData = this.fetchData.bind(this);
  }

  getInitialState() {
    return {
      // Card is initially set to null so that the loading message shows.
      card: null,
    };
  }

  componentDidMount() {
    this.fetchData();
  }

  // This is where the magic happens! Fetches the data from our API and updates the application state.
  fetchData() {
    this.setState({
      // We'll also set card to null when loading new cards so that the loading message shows.
      card: null,
    });
    fetch(REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        // this.setState() will cause the new data to be applied to the UI that is created by the `render` function below.
        this.setState({
          card: { pic: responseData[0].guid.rendered }
        });
      })
    .done();
  }

  // Instead of immediately rendering the template, we now check if there is data in the 'card' variable
  // and render a loading view if it's empty, or the 'card' template if there is data.
  render() {
    if ( !this.state.card ) {
      return this.renderLoadingView();
    }
    return this.renderCard();
  }

  // The loading view template just shows the message "Wait for it..."
  renderLoadingView() {
    return (
      < View style={styles.container}>
        < Text style={styles.text}>
          Wait for it...
        < /Text>
      < /View>
    );
  }

  // This is the original render function, now renamed to renderCard, which will render our main template. 
  renderCard() {
    let quote = this.state.card.pic;
    return (
      < View style={styles.container}>
        < View style={styles.imageContainer}>
          < Image style={{width: windowSize.width, height: windowSize.height}} source={{uri: this.state.card.pic}}  />
        
        < View style={styles.buttonContainer}>
          < TouchableHighlight
            style={styles.button}
            underlayColor='#ccc'
            onPress={this.fetchData}
          >
            < Text style={styles.buttonText}>Next quote
          < /TouchableHighlight>
        < /View>
      < /View>
    );
  }
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#FFFFFF',
  },
  text: {
    fontSize: 18,
    paddingLeft: 20,
    paddingRight: 20,
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  imageContainer: {
    alignItems: 'center',
    flex: 1,
    width: windowSize.width,
    height: windowSize.height,
  },
  buttonContainer: {
    bottom: 0,
    flex: .1,
    width: windowSize.width,
    backgroundColor: '#1488BC',
  },
  button: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  buttonText: {
    fontSize: 30,
    color: '#FFFFFF',
  },
});

NOTE: The plugin I’m using to prettify the code doesn’t work well with the < view> tags and everything in between, so I added a space to the first character so they would appear. Make sure to remove that space in your own code.

After saving this, check your device to see if it looks something like this (your image may be different):

If everything connected correctly, you should see an image from the WordPress media library on the screen with a blue button that says, “Next quote.” If you click the button, the same image loads over and over again. While it’s great that we’re connected, it’s not the ideal outcome. We want the app to display a random image from the media library with each click.

Adding customized endpoints to the WP REST API

In order to get our button to work, we need to gather all the media IDs and randomize them for each click of the button. Unfortunately there’s no API endpoint that allows us to just grab an array of media IDs. No problem, we’ll just write a WordPress plugin that will provide those endpoints for us. There’s a lot of great documentation for this. Let’s do that now.

I’ve already created a plugin to do this, https://github.com/mapk/wp-rest-api-v2-media-ids or in the WordPress Plugin repository https://wordpress.org/plugins/wp-rest-api-media-ids/, but if you’d like to learn how, let’s dig into it.

Create a folder inside your plugin directory on your WordPress site and call it something like, media-ids-api-endpoints, and then create a php file inside there with the same name, media-ids-api-endpoints.php.

Now we can add our plaintext field back into the post endpoint, this time using the register_api_field function provided by WP-API. The WP-API docs recommend that new fields be registered at the rest_api_init action, so we’ll create a function that contains our field registration and pass it to that action and another function that will actually return the plaintext content.

First we’ll use the register_rest_route function to register a new route at wp-json/media-ids/v1/get-all-media-ids in our new plugin php file.

add_action( 'rest_api_init', function () {
    register_rest_route( 'media-ids/v1', '/get-all-media-ids', array(
        'methods' => 'GET',
        'callback' => 'dt_get_all_media_ids',
    ) );
} );

Next, we’ll want to write the function that gathers those IDs, and wrap the code with some boilerplate text with the plugin information. In the end, it should look like this:

/*
Plugin Name: WP-REST-API V2 Media IDs
Version: 0.1
Description: Adding an endpoint to show all media IDs on WP REST API v2
Author: Mark Uraine
Author URI: http://markuraine.com
*/

/**
 * Get all media IDs
 * @return array List of media IDs
 */

// Return all media IDs
function dt_get_all_media_ids() {
    if ( false === ( $all_media_ids = get_transient( 'dt_all_media_ids' ) ) ) {
        $all_media_ids = get_posts( array(
            'post_type'   => 'attachment',
            'post_mime_type' => 'image',
            'post_status'    => 'inherit', 
            'posts_per_page' => -1,
            'fields'      => 'ids',
        ) );
        // cache for 2 hours
        set_transient( 'dt_all_media_ids', $all_media_ids, 60*60*2 );
    }

    return $all_media_ids;
}

add_action( 'rest_api_init', function () {
    register_rest_route( 'media-ids/v1', '/get-all-media-ids', array(
        'methods' => 'GET',
        'callback' => 'dt_get_all_media_ids',
    ) );
} );

NOTE: Make sure to append a < ?php without the spacing to the beginning of the code as well (on top of the comments). Again, the code prettifier didn't recognize this tag.

Once this is all set up, you should be able to visit: [your-domain.com]/wp-json/media-ids/v1/get-all-media-ids to see the API result. You can view mine here: http://leadershipquotes.mystagingwebsite.com/wp-json/media-ids/v1/get-all-media-ids. It should look something like this; an array of numbers:

Updating the app to use customized endpoints

Now we just need to update our react native app to utilize this new endpoint and make cacheable requests. First we’ll replace the REQUEST_URL variable with REQUEST_URL_BASE and add two more variables for the different endpoints we’ll be accessing:

const REQUEST_URL_BASE  = 'https://leadershipquotes.mystagingwebsite.com/wp-json/';
const POSTS_URL_PATH    = 'wp/v2/media/';
const GET_MEDIA_IDS_PATH = 'media-ids/v1/get-all-media-ids';

Then we’ll need to add two new properties to the initial state of the app: cardIDs and currentID:

getInitialState() {
    return {
      // Card is initially set to null so that the loading message shows.
      card: null,
      cardIDs: null,
      currentID: null
    };
  }

Next, we’ll add functions to populate those properties in the same App.js file:

getAllIDs() {
  fetch(REQUEST_URL_BASE + GET_MEDIA_IDS_PATH)
  .then((response) => response.json())
  .then((responseData) => {
    this.setState( {
      cardIDs: responseData
    } );
  })
  .then(this.fetchData)
  .done();
}

getRandID() {
  let currentID = this.state.cardIDs[Math.floor(Math.random()*this.state.cardIDs.length)];
  if ( this.state.currentID == currentID ) {
    currentID = this.getRandID();
  } else {
    this.setState( {
      currentID: currentID
    });
  }
  return currentID;
}

You’ll notice that the last thing getAllIDs() does after it has retrieved the list of IDs and added them to the state is that it calls this.fetchData() – this is because fetchData() will need the cardIDs to already be available in the state before it can run. We’ll need to replace fetchData() with getAllIDs() in the componentDidMount() method to make sure that getAllIDs() runs first:

componentDidMount() {
  this.getAllIDs();
}

And then we’ll need to update fetchData() to run getRandID() and then use the random ID to fetch that media item:

fetchData() {
  let currentID = this.getRandID();
  this.setState({
    // We'll also set card to null when loading new cards so that the loading message shows.
    card: null,
  });
  fetch(REQUEST_URL_BASE + POSTS_URL_PATH + currentID)
    .then((response) => response.json())
    .then((responseData) => {
      // this.setState() will cause the new data to be applied to the UI that is created by the `render` function below.
      this.setState({
        card: { pic: responseData.guid.rendered }
      });
    })
  .done();
}

Not much has changed in fetchData(), we’ve added the call to getRandID() at the beginning and then we’re using the output of that along with REQUEST_URL_BASE and POSTS_URL_PATH to access a specific post via the API with a url that is easily cacheable by a server-side caching system such as WP Super Cache or Varnish. Notice that we also removed the array number from responseData.guid.rendered.

Save all that out, and your app should refresh in Expo on your device. Start clicking the button and watch each new image display on screen in random order. You did it!

For a complete view of both the React Native app and the WordPress plugin, refer to the sources below.

React Native app
https://github.com/mapk/Leadership-Quotes

WordPress plugin
https://github.com/mapk/wp-rest-api-v2-media-ids