Herd those API calls! Transitioning to VueX

Or, “How not to build a web app”

You’ll know when you need them.”

A friend recently got glasses, and she was surprised. She didn’t know she needed them, but afterwards felt like the world was in HD.

I’ve lived a lot of my coding life like her. I didn’t know what I didn’t know.

With this Wordpress+Vue theme I’m building out, I’m taking the time that I often don’t afford myself with client/work/on-deadline projects.

Reinventing the wheel isn’t fun, and neither is refactoring 3 times in 2 weeks after an early-optimization. Good thing too.

That “Single Source of Truth” tho..

  1. On-load (no AJAX),
  2. Direct-from-API (AJAX),
  3. API-via-Local Store (Cached & Managed).

I knew I wasn’t “doing it right” when I released code with the “Parallel, Self-Contained” model. But the data was staying on-page, and it wasn’t really hurting anything except load-times.

For a brief moment, I started to bootstrap some data on-load in the PHP-based index.php file. That was the really, really bad “Dual-Ended” approach which would destroy my sanity.

I realized that js variable was the basis for my “Single Source of Truth” and my API calls needed to dump into the initial on-load data variable at a bare minimum, a la the “Parallel, Centralized” approach.

But I also had moved all API-service calls together, and had thereby moved onto the “Service-Oriented” model.

That left me either making my own store-on-a-variable home-brew method, or to jump on the VueX train and learn about client-side data-stores.

Coding well includes knowing when you need to stop and look ahead.

What’s in a Store?

The data of the current view

The state of the current view

The “is” concept. (is loading?, is ready?, is current?, etc)

Shared data

We’ll be rolling forth on the “shared data” for the most part.

Code Dive-In:

// Direct-from-API Patternlet wordpressService = {  getFromAPI: function( path, resolve, reject ){    Vue.http.get( path ).then(response => {
let responseData = {posts: response.data, totalPages: 1};
resolve( responseData );
}).catch(error => reject(error));
getPageBySlug: function(page_slug) {
let path = "/wp-json/wp/v2/page/?slug="+page_slug;
return new Promise((resolve, reject) => {
this.getFromAPI( path, resolve, reject );
};// Component: => Service => API
import WordpressService from '../services/wordpress';
export default {
props: ['post_slug'],
created: function(){
methods: {
getPageBySlug: function() {
const wpPromisedResult = WordpressService.getPageBySlug( this.post_slug );
wpPromisedResult.then(result => {
if( result.posts.length > 0){
this.page = result.posts[0];
}).catch(err => {
this.error = true;
}// getPage

When there is just the data-source and the presentation, it’s easy to understand: a component data becomes visible when the data shows up.

When there’s a Service & Store involved, it’s now a 3-body system (ask a Mechanical Engineer). Things are getting wonky.

How it actually happens

Redefining the API/Service direction & having 2 functions with the same name looks redundant. I don’t recommend it, but I did it this way to show the parallels. A better approach may be to load a larger volume of data from the API, and use the store for specifics.

Most of my code for the Store is based off of Ogundipe Samuel’s very clear code from Getting Started with VueX.

// VueX Store Pattern:Vue.use(Vuex);
const store = new Vuex.Store({
state: {
posts: [],
posts_loading: false
mutations: { // Used by store.actions
STORE_POSTS: (state, { posts }) => {
actions: { // Used by Components via dispatch()
FETCH_POSTS: function ({ commit }, get_object) {
const found_post = this.getters.getPost( get_object.type, get_object.slug );
if( typeof found_post !== 'undefined' ){ // In-Memory already
commit('SET_POSTS_LOADING', false);
}else { //no matches, hit the API
commit('SET_POSTS_LOADING', true);
.getPost( get_object.type, get_object.slug )
.then((response) => {
if( response.posts.length == 0 ) // OPTIONAL: Handle 404s
commit('STORE_POSTS', { posts: response.posts }); }, err => { // seems to be rare in normal circumstances.
console.log("FETCH_POST: err:", err, get_object );
commit('SET_POSTS_LOADING', false);
}, getters: { // Used by Component
getPostBySlug: (state) => (slug) => {
let foundit = state.posts.find(post => ( post.slug === slug && post.type == 'post')); if(typeof foundit == 'undefined') store.dispatch('FETCH_POSTS', { slug: slug } ); return state.posts.find(post => post.slug === slug);
export default store

The Component scripting becomes quite small, so long as the template has all the data it needs, which is assumed in this semi-contrived example:

// Component >> Store >> Service >> API<template>
<div class="page-wrapper">
<div class="article" v-if="(!posts_loading && this_post)">
<h1 class="article-title">{{ this_post.title.rendered }}</h1
import Vuex from 'vuex';
export default {
props: ['post_type', 'post_slug'],
{ type: this.post_type, slug: this.post_slug} );
}, computed: {
this_post: function(){ //Get the data from Store
let foundPostInStore = this.$store.getters.getPostBySlug(this.post_slug);
return foundPostInStore;

The Full Build-Out

  • store.posts[]={id, post_type, post_meta, etc}
  • store.terms[] = { term_slug, taxonomy_name }
  • store.authors[] = {}

Taxonomies (a group of terms) just filter/reduce:

const store = new Vuex.Store({
getters: { // Used by ComponentgetTerms: (state) => ( requested_taxonomy_name, requested_term_slug ) => {
let found_term = [];
if( state.terms.length > 0 ){
found_term = state.terms.reduce(
function( accumulator, currentTerm ) {
if( currentTerm.taxonomy == requested_taxonomy_name && currentTerm.slug == requested_term_slug ){
accumulator.push( currentTerm );
return accumulator;
}, [] );
return found_term;
} //getters}); //store

I used the same approach for all archives: given a requested_author_slug , return all the posts in the Store that match. I’m also certain that lodash has a better method for my pure reduce. Note: UI pagination required a different approach.

The major upside?

  1. Pre-fetch! The UI pagination can be 4 posts per page, but the API pagination can be 20.
  2. Use the data across the whole session.

The major down-sides to this?

  1. Parallelism. I’m now writing the same function 2–3 times for each data type just to pass data along.
Parallelism is annoying

The WordPress API basically exposes each core database table. Nothing crazy. The basic routes follow the same pattern. Adding a Service to talk to the API is required, and adding a local Store is just another way to access the same thing.

How many times you want to refactor depends on how many times you want to re-implement data-access methods.

Choose your tradeoffs well. If the user will be blown away by 1 second load times over 0.1 second load times, then you’re good with infrastructural (CDN+Browser) caching for API requests.

You have 1 second. Choose Wisely.

Elephant in the room (may-or-may not be bull in china shop)

Once you have a handful of stores or branches in your state tree, you end up practically duplicating your server-side business data and relationships on the client. — Mark Johnson

This is shown in its worst form with search. If the search component was hitting the API directly, WordPress itself is doing the search. Sure it’ll take an extra second and perhaps that’s fine but any store.getters.search function I would write is only searching the posts in-memory.

Wait, I’m building my own search algorithms?

This all makes sense. There’s this crazy guy who implemented his own WordPress API using GraphQL. I finally understand why. Generic REST routes can be like eating a plate of spaghetti one noodle at a time, or eating the AlphaBits cereal alphabetically.

Perhaps the real answer to the search problem is to just return the Post IDs?


This nice how-to shows the inter-component messaging alternative to what I’m doing:

a good place to start looking for application state is your component props, and more specifically, props across multiple components that share the same data

A few common “gotchas”:

  • Hit the store/api during created() not mounted() You might also need to hit the store on beforeRouteUpdate(to, from, next).
  • Each component’s default state moved out of data(); now the default state is in computed:{}.
  • Chaining computed variables that are Promise-based still needs the Promise. Maybe using “data_loading” variables just needs to be a Promise!

Next Steps

Thanks for reading! You can compare the end-user difference the previous version:

..or checkout the code on it’s own branch:

(Front|Back|Web) × (Developer|Ops) @WierStewart