WordPress Data Store Properties: Controls

This entry is part 7 of 15 in the series, A Practical Overview of the @wordpress/data API

Often with any sort of state in a given application, there can be times in an application’s lifecycle where you want to be able to execute some sort of logic as a part of the flow in setting state or updating state. A good example of this is asynchronous data flows between the client and server sides of your application (eg. persisting state to a server database via a REST API request).

In wp.data, a control (or control function) defines the execution flow behavior associated with a specific action type. You can then use the control action creators in store action creators, and resolvers, and you can register defined controls to handle this execution as instructed within your store.

Let’s take a look at an example control action and corresponding control function found in the src/data/controls.js file within the example app:

export const fetch = (path, options = {}) => {
  if (options.body) {
    options.body = JSON.stringify(options.body);
    options.headers = { "Content-Type": "application/json" };
  }
  // ensures things work with cors.
  options.credentials = "omit";
  return {
    type: "FETCH",
    path,
    options
  };
};

export default {
  FETCH({ path, options }) {
    return new Promise((resolve, reject) => {
      window
        .fetch(path, options)
        .then(response => response.json())
        .then(result => resolve(result))
        .catch(error => reject(error));
    });
  }
};

At the top of the file is the fetch control action creator which receives the same argument shape as the window.fetch function and then returns a control action object that has FETCH as the action type, and passes along the path and options property and values.

Then we are exporting a default object that is our control object that has all the defined control functions. In this object is a FETCH property that has a function as a callback, this function is the control function that will get invoked when the control action with the FETCH type is returned or yielded from an action creator or a resolver.

This default object is what would get registered to your store as the value for the controls object. Let’s go over a few rules of this object:

  • Each property is the same value as the control action type you want the defined control function to react to.
  • The value attached to each property is a control function which receives the original action object associated with the control action type.
  • The control function should return ether a Promise or any other value.

In this series, we will have opportunity to implement controls in the example application, however to help with visualizing how controls are implemented, here’s a quick look at an example store action creator that has been updated to be a store action creator generator implementing controls:

import { fetch } from './controls';

export function* updateAndPersistThing = ( thing ) => {
    // catch any request errors.
    try {
        // execution will pause here until the `FETCH` control function's return
        // value has resolved.
        const updatedThing = yield fetch( 
            'https://thingupdaterapi.com/things',
            { method: 'PUT', data: thing }
        );
    } catch( error ) {
        // returning an action object that will save the update error to the state.
        return { type: 'THING_UPDATE_ERROR', message: error.message };
    }
    if ( updatedThing ) {
        // thing was successfully updated so return the action object that will
        // update the saved thing in the state.
        return { type: 'UPDATE_THING', thing }
    }
    // if execution arrives here, then thing didn't update in the state so return 
    // action object that will add an error to the state about this.
    return { type: 'THING_UPDATE_ERROR', message: 'Thing did not get updated.' }
}

You implement control action creators in either action creators or resolvers that are defined as generators which yield action types. When wp.data encounters an action creator (or resolver) as a generator as a part of the execution flow, under the hood it steps through each yielded value and for controls that return a promise, it will await the resolution of the promise and return that as the value of the yield assignment.  If the control handler returns undefined, the execution is not continued.

Sidenote: Generators are a whole subject on their own that is good to have an understanding of to fully comprehend how wp.data is using them. A good resource for understanding generators is this MDN article or this post.

Controls can be one of the harder interfaces of wp.data to understand, yet once you do, there is immediate benefit in having a clearer flow of execution for actions and resolvers having side-effects.

The good news, is there is a package available that exposes three commonly used controls for usage in your stores,@wordpress/data-controls. The controls exposed in this package will cover most of the use-cases you might have for controls in a custom data store, in a WordPress environment. The controls exposed by this package are:

apiFetch

This control action creator will yield an action type that triggers a control function for performing an api fetch call using the object provided to the action creator. Essentially it uses the @wordpress/api-fetch interface, but wrapped in a control for usage in wp.data action creator and resolver generators.

It should be noted that this implementation is currently coupled to interacting with the WordPress REST api (PUT, and DELETE methods are sent on the request as a header instead of as the request type and the service we’re using doesn’t know about the header). Thus for the example application used in this series, I’ve created a custom control that implements window.fetch directly. This is the example referenced at the beginning of this article.

dispatch

This control action creator will yield an action type that can be used to dispatch another action in a different store within the same data registry.

select

This control action creator will yield an action type that can be used to select data from the state in another store within the same data registry. 

Note: this control will automatically detect if the selector is attached to a resolver and will wait for the resolution to finish before returning the value of the selector. We haven’t addressed resolvers yet in this series, but don’t worry, we’re going to jump into that in the next post!

The best way to understand the usage of controls is to, well, use them. So in the next post we’ll jump back to learning about the resolvers property in the registerStore configuration object.

WordPress Data Store Properties: Selectors

This entry is part 6 of 15 in the series, A Practical Overview of the @wordpress/data API

The job of selectors is to provide an interface to access state from the registered data store. With wp.data this is the primary way that your store state is exposed and allows you to encapsulate logic surrounding exposing that data. For instance, let’s say you just want to store all products in the store state as a simple array of product objects:

const state = [
    { id: 1, name: 'Scarf', price: '1025' },
    { id: 5, name: 'Hat', price: '356' },
];

You could create a selector that returns the entire state and then any components wanting just a specific product could filter that state:

export const ProductName = ( { productID } ) => {
    const products = useSelect( ( select ) => select('mystore').getAllProducts(), [] );
    //get just the product we want to use.
    const product = products.filter( ( product ) => product.id === productID );
    return <h1>{ product.name }</h1>
};

Sidenote: In the above example useSelect is something you might not know about yet. I will cover this later in the series but for now, it’s sufficient to understand that getAllProducts is the selector being called here.

In the example, we have a getAllProducts selector that is returning all the products stored from the store state and then the component is taking care of filtering that to just the product needed. However, I can do better, if we move the filtering logic into a selector:

export const ProductName = ( { productId } ) => {
    const product = useSelect( ( select ) => select( 'mystore' ).getProduct( productId ), [ productId ] );
    return <h1>{ product.name }</h1>;
};

Now instead of having to do the same filtering logic in every component where I need a single product from the store state, I can just use the getProduct selector.

Sidenote: I usually avoid getting too granular with the created selectors initially to avoid over-engineering and creating selectors that might never be needed. So even with the example above, I probably would have just started with a getProducts selector first and then only add the more granular getProduct selector if I saw a repeated pattern in my components of getting a single product out from the array of products.

So selectors are basically your window into retrieving what slice of state you need from the store. With that in mind, let’s dive into creating our selectors in our app. First, here’s the rough shape of what selectors look like:

export const someSelector = ( state, name ) => {
    return state[ name ];
};

Let’s break this down:

  • the selector is a function (preferably pure).
  • every selector receives the current state as the first argument.
  • you can define an arbitrary number of arguments the selector receives depending on your needs. In this example, our state is an object and I’ve indicated the first argument is a name used to return a specific property value from the state object.
  • the selector returns a value from the state using the arguments provided.

Sidenote: When the wp.data api calls your defined selector, it will automatically inject and pass along the current state as the first argument along with any other arguments the selector is invoked with. So even though you define your selectors with state as the first argument, you will call your selectors without the state argument (you’ll actually be calling a created function created by the wp.data api that maps to your selector).

With this in mind then, going to our app, what is one selector you know we’ll need for sure to interface with the state?

Something to retrieve the products right? So let’s create a selector that gets the products from the store state. We’ll create a file for holding our selector called selectors.js. You should now have the following folder structure in your app: src/data/products/selectors.js. If it isn’t already, go ahead and open up the selectors.js file. Go ahead and give a go at creating your selector and come back when you’re done (don’t cheat and read ahead! Try to see how well you understand the concepts so far first).

All done? If I’ve done a good job explaining selectors so far in this, and you have the same idea of what shape the state will be in our store as I do, then in this file, you’ll probably have at least something like this (might not be exactly the same, but generally the same thing):

export const getProducts = ( state ) => state.products || [];

Let’s break this down. I decided that I’m going to store products in the state in a property called products (how original!). I also indicated what will be returned if the products property isn’t defined yet (or is non-truthy) in the state. I like returning the same data type as what I expect to store things in so here, I’m returning an empty array. From this you can infer that we plan to store products in the state as an array.

Sidenote: Why am I storing products as a property on the state instead of just on the root object as an array of product objects? I tend to follow this pattern for the state shape in created stores to avoid painting myself in a corner in future iterations where I might want to keep additional data in the state. This often limits any refactoring I might have to do down the road.

Now that we have our selector in place for our app, we likely are going to initially retrieve the products from somewhere (via an REST request) right? Something unique to wp.data that helps with this is a thing called resolvers.  But before we jump into the world of resolvers, we need to first learn a little bit about the controls property. This will be the next post in the series.

WordPress Data Series Overview and Introduction

This entry is part 1 of 15 in the series, A Practical Overview of the @wordpress/data API

For the past couple of years, my involvement with the Gutenberg project in WordPress has frequently taken me into the innards of @wordpress/data.  I’ve had to use it for projects integrating with the new WordPress Block Editor (Gutenberg), new interfaces and user experiences utilizing the new WordPress JavaScript packages, and I’ve also been directly involved with various improvements via contributions to the package itself.

In that time, I’ve had the opportunity to learn quite a bit about this package and the promise it holds for data and state management in the WordPress future and at the same time I’ve also observed the apparent scarcity of really exhaustive and thorough documentation about it.  This has surfaced multiple times as I’ve interacted with teammates or various WordPress developers who have various questions about how to use it. 

So, this series of posts is something I’ve been working on for a while that I hope will help close the gap for more people on understanding what @wordpress/data is and how to use it. Since there’s a lot of material to cover, I’m splitting this series up into smaller chunks and publishing each post in the series over time. 

Read more “WordPress Data Series Overview and Introduction”

Into the next decade…

It’s been a while since I’ve written one of these year end, year beginning posts, but this is a significant year as the 2010’s draw to a close and the 2020’s have begun. So I figured it be a good exercise to reflect and predict. Let’s start with reflect:

Reflect

I’m going to group things by Personal, Career, and General. Personal will be things that have happened in my family, and life more close to home. Career will be things that have happened in my work life. General is just general observations about the world as a whole.

Read more “Into the next decade…”

Fantastic hooks and where to use them.

This post is an introduction to the useDispatch and useSelect hooks in the wp.data api found in the WordPress Gutenberg plugin. Note: the hooks will be available with the next release of the @wordpress/data package (which will happen after Gutenberg 5.9 is released)

So you’ve had a chance to finally figure out the wp.data api. You’ve written a few components using withSelect or withDispatch. You think you’ve got a handle on some of the finer points of higher order components in Gutenberg (and realize that’s essentially what withSelect or withDispatch are!). But now you start seeing things like useDispatch or useSelect pop up in use. What the heck are these? Where did they come from? Is everything you learned useless?

To answer the last question and appease your worries. No. Everything you’ve learned in using withSelect and withDispatch is still awesome. You can keep using them and your Gutenberg blocks and apps will still keep on rocking.

Read more “Fantastic hooks and where to use them.”

Testing Javascript for your Gutenberg Integrated Plugin

(Edit October 10, 2018) Note: This article is now out of date but is kept published for reference purposes. Nearly everything in Gutenberg is published as a package so for the purpose of testing you can include those packages as a devDependency in your package.json file and jest will know to reference those in tests. 

As a part of my work with Event Espresso I’ve been assisting them with moving over to a more modern Javascript build system.  This has been prompted by the upcoming new editor for WordPress codenamed Gutenberg.  I’m not going to spend a whole lot of time talking about Gutenberg since there’s already a ton of information on the internet.  What I want to do in this post is outline a way for plugin developers to test their custom components/blocks built for Gutenberg using the Jest framework.

Writing javascript tests using Jest is pretty straightforward and this isn’t a how-to-post since there’s a lot of that already available (including this readme the Gutenberg team prepared).  Instead I thought it’d be useful to write how I solved a problem with a particular set of tests I was trying to write for some code I had written (especially since I couldn’t find anything useful on the internet myself to help with it).

Read more “Testing Javascript for your Gutenberg Integrated Plugin”

Something every Language/Library debate needs to keep in mind….

There’s some discussion happening right now in the WordPress world about what javascript framework to add to WP core.  These kind of debates happen frequently in the programming world (Google “PHP sucks” for some great examples).  In the course of these types of discussions, support is usually brought up for one point of view or another by pointing to what others have written about it on the internet.  Certainly it is reasonable to glean from what has been written,  but I think it’s also important to recognize there is a silent majority of programmers out there that we’ll never get input from as demonstrated by this simple diagram I did up (horrendous but communicates the point I want to make):

peoplewhoprogrampeoplewhowrite

The intersection of people who write (and you might add write well to that) and those who program (and you might add those who program well to that) is a small portion of the entire picture.  Sure, we don’t discard what is written about, but let’s not give it undue weight either.  Ultimately, there is a silent majority of people building solutions who really don’t care about these discussions or flame wars because they are using what gets the job done.

At the end of the day, use what gets the job done.  Learn it well.  Learn it deeply.  If you’re one of the few who also has the gift/time to write, write about it!

On Vision

This is a post that has been percolating as a draft post for nearly two or three months now.  But as a theme, its something I’ve wrestled with far longer.  Those who know me well, know that at my core, I’m kind of a systems and strategy kind of guy. What makes gets my ticker picking up its pace,  is when I get to work with a big picture idea and help generate/coach the strategies and systems to see that that big picture come alive and grow.  And so, that’s why this post reflects something that strums the passion bone in my body.

I want to spend a little bit of time articulating some things I’ve come to believe about a word that gets used a lot,  but is rarely understood fully.   This post is sparked in part because I think there’s a lot of misunderstanding about what vision is, and what it is not.  Part of that is because its so elusive, and part of it is because it frequently rides the buzzword wave.  So here’s what I think about vision:

Read more “On Vision”

Using circleci.com for automated WordPress plugin testing.

A few months ago, one of the teams I work with went on the hunt for a good continuous integration service for running tests on the code we write.  We jumped on the unit test bandwagon at the beginning of the year and wanted to really amp up the quality of our product by having tests run on every commit.  I was tasked with this job (and anyone who knows me knows I LOVE playing with new things, so it was a task I was looking forward to doing)

Most WordPress users are familiar with travis-ci.org and the internets were full of instructions for getting things plugged in and up with travis.  Unfortunately, our project is inside a private github repo so we couldn’t use the free travis plan to run our tests on and the premium plan was a bit to pricey for our first attempt at this.  So after searching around, I stumbled on circle.  From all appearances, circle looked like it would work very similarly to travis and bonus points were that their plans are much cheaper – so great for getting started with.

Read more “Using circleci.com for automated WordPress plugin testing.”

If I was in charge of a telecommunications company…

Yes, this is one of those “If I was in charge, I’d do this…” posts.  Don’t we all have opinions on how companies should be run?  Just had these thoughts today so decided to write them down.  Canada is known as one of the worst places for mobile phone plans (from a consumer perspective), largely because of the lack of competition.  If by some freak cosmological accident I ended up in charge of a telecommunications company, here’s some things I’d do:

I’m not going to talk about pricing or packages or anything like that, because at the end of the day what matters most for company longevity is customer loyalty and word of mouth marketing (even more so in today’s hyper social world).  The only reasons why I think the existing companies are doing so well in Canada now are because of their entrenched position with infrastructure, and the lack of choice for consumers.  With that said,

1. I’d ensure that our company has some sort of automated process that scans existing customers accounts (ooo privacy, but bear with me) and if there are any current company promotions that are better plans (either in terms of the same as what they have but cheaper, or more features than what they have for same price) than what the customer currently has we automatically switch them to the cheaper plan and notify them.

2. The plans would be simplified (which would need to happen for number 1 to work effectively).

3. Company wide policy that if a customer calls and complains about something not working, and they have ANY difficulty getting the level of service they asked for, we immediately grant them at least a free month’s service.

4. For customers who have been with the company for over three years.  We surprise a certain number of them randomly once a year with free phones and/or upgraded plans for their current phones.

That’s just four things.  Not a lot, and I fully realize there’s a lot more involved with running a company, but four things that I think would help towards gaining long term customers and viral word of mouth marketing.

What would you add?