Introduction to React, part 3

Published in WordPress.

Watch our video

There is a better version of your web

Share this post

Last week we saw that a React component is nothing more than the graphical representation of (part of) our application state. In the example that we implemented, a component showed the value of a counter along with two buttons that allowed to increase or decrease said value. The trick was simple: a component’s props can be both data and functions.

So far, the two main ideas we’ve learned from Part 1 and Part 2 of this tutorial are:

  • A React component is a pure function that receives a set of properties and generates the HTML required for rendering it.
  • The properties a React component receives can be (a) the data it should show in the UI and (b) functions that, once connected as callbacks to DOM events, modify our app’s state.

Well, in this third part we are going to expand our knowledge about this architecture a little more and we are going to learn how to use WordPress stores. With them, we can define the state of our application in a clean, testable, and UI-independent manner.

Extending our Example

In the second part of this tutorial we created a counter with a couple of buttons to increase or decrease its value. To do this, we added a mutable variable in index.js file along with a function to mutate it. Our Counter component:

export const Counter = ( { value, onIncrease, onDecrease } ) => (
  <div>
    <div>Counter: <strong>{ value }</strong></div>
    <button onClick={ onIncrease }>+</button>
    <button onClick={ onDecrease }>-</button>
  </div>
);

could then receive all the props it needed:

// Import dependencies
import { render } from '@wordpress/element';
import { Counter } from './components/counter';

// Store value
let value = 0;
function setValue( newValue ) {
  value = newValue;
}

// Render component in DOM
const wrapper = document.getElementById( 'react-example-wrapper' );
render(
  <Counter
    value={ value }
    onIncrease={ () => setValue( value + 1 ) }
    onDecrease={ () => setValue( value - 1 ) }
  />,
  wrapper
);

Well, today we are going to learn how we can implement the state management properly and better. But first, let me aim at a more complex example: let’s expand our app so that it can have multiple counters at once and see how we can manage them all using a WordPress store. The final result (which we will implement in the next post) will look similar to this:

Multiple counters with React and Redux
Multiple counters implemented with React components and a Redux-based WordPress store.

Defining the State of our Application

When facing a problem like this, the first thing you should think about is how you’ll manage the state of your app. And, assuming you can only read and write the state using functions, the easiest way to do so is by thinking about its API: i.e. the functions that allow you to query and update it. In our case, I might suggest:

  • Adding a new counter
  • Deleting counter x
  • Set the value of counter x
  • Get a list of all the counters our app has
  • Get the value of counter x

but you might want to use a different approach (for example, you might want to create a function for incrementing a certain counter and a different function for decrementing it).

Once you’ve defined this interface, you should think about the information you need to keep track of this state and, in particular, the data structure you’ll use to do so. In our example, we want to keep track of several counters and, for each counter x, we want to know its value. What data structures could help us with this endevour?

Depending on what this x is, you might want to use one data structure or another. If, for example, x is the index of the counter (that is, you want to be able to retrieve the value of the first, second, or third counter), an array of numbers might be enough. If you want x to be a unique counter identifier, you might want to use a different approach. For example, you might want to use a dictionary with id/value pairs:

const counters = {
  ae13a: 0,
  f18bb: 3,
  e889a: 1,
  8b1d3: -5,
};

or an array of objects:

const counters = [
  { id: 'ae13a', value: 0 },
  { id: 'f18bb', value: 3 },
  { id: 'e889a', value: 1 },
  { id: '8b1d3', value: -5 },
];

If you first define the interface (setters and getters, if you will) to manipulate and access your app’s state, then how you implement the state itself is a black box. This means you can change the store itself at any time and things will work as expected as long as you maintain the API.

Creating a Redux-based Store with WordPress

WordPress’ data module allows you to define one or more stores to manage the state of your application. To create a new store using this package, you simply need to use the registerStore function with the following arguments:

  1. A name that uniquely identifies your store
  2. A selectors object with all the getters to retrieve data from the store
  3. An actions object with function that trigger update requests (we’ll talk about what this means later on this post)
  4. A reducer function that’s responsible for updating the state when certain actions are triggered

Let’s see a real example, shall we? Continuing with last week’s example, create a new store folder in src. Then create an index.js file in it with the following code:

// Load dependencies
import { registerStore } from '@wordpress/data';

import reducer from './reducer';
import * as actions from './actions';
import * as selectors from './selectors';

registerStore( 'react-example/counters', {
  actions,
  reducer,
  selectors,
} );

The snippet is quite straightforward, isn’t it? We simply import the registerStore function we were talking about and a few dependencies we haven’t created yet (reducer, actions, and selectors), and registers the new store. Notice how we’ve named our store to make sure it’s unique: we’ve combined the name of our plugin (react-example) with a word that defines what the store is about (counters). Easy peasy!

Actions in a Store

One of the things that (in principle) every store needs is a set of functions that allow us to modify its state. In our example, actions will be insrc/store/actions.js:

export function addCounter( counterId ) {
  return {
    type: 'ADD_COUNTER',
    counterId,
  };
}

export function removeCounter( counterId ) {
  return {
    type: 'REMOVE_COUNTER',
    counterId,
  };
}

export function setCounterValue( counterId, value ) {
  return {
    type: 'SET_COUNTER_VALUE',
    counterId,
    value,
  };
}

As expected, our store has three actions to update its state:

  • addCounter: a function that adds new counters in our store. The only argument it takes is the ID of the new counter.
  • removeCounter: a function that removes an existing counter. Again, the only argument it takes is the ID of the counter we want to remove.
  • setCounterValue: a function that sets a new value to a given counter. Obviously, this function takes two arguments: the ID of the counter to update and its new value.

Now, if you look closely at each action you might be surprised: none of these actions seem to update anything. Instead, they return objects. What’s going on here?

In Redux (and WordPress stores are based on Redux), actions don’t modify a store directly. Instead, they generate an object that signals an “update request.” These requests always follow the same pattern: they are an object with a type attribute that uniquely identifies the request and as many additional properties as necessary to successfully apply the requested update.

So let’s now see how one can actually update the state…

Implementing the Reducer to Update a Store’s State

If store actions only represent an update request, we need someone or something to actually update our store’s state when such a request is dispatched. That’s what a reducer is used for.

A reducer is a function that takes the current state of our application and an action that someone has dispatched and updates the state by applying the requested update.

In the previous section we have seen that our store has three actions, so our reducer must be able to apply each of them:

import { omit } from 'lodash';

export default function reducer( state = {}, action ) {
  switch ( action.type ) {
    case 'ADD_COUNTER':
      return {
        ...state,
        [ action.counterId ]: 0,
      };

    case 'REMOVE_COUNTER':
      return omit( state, action.counterId );

    case 'SET_COUNTER_VALUE':
      return {
        ...state,
        [ action.counterId ]: action.value,
      };
  }

  return state;
}

As you can see, the reducer receives the previous state (which, by the way, by default is the empty object {}) and the dispatched action with the information to update the state. The body of the reducer is quite simple:

  • It starts with a switch statement to discern the type of update (action.type) we should run:
    • If it’s ADD_COUNTER, it generates a new state object with a new key action.counterId with its counter value set to 0.
    • If it’s REMOVE_COUNTER, it generates a new state object without the key action.counterId.
    • If it’s SET_COUNTER_VALUE, it generates a new state object where the value in action.counterId is now set to action.value.

The most important thing here is to realize that a reducer is (and must be) a pure function. This means that any modification we make to the current state involves building a new state. Under no circumstances, then, should you mutate the previous state.

Selectors in a Store

Now that you know how to update the state of your store, all you have to learn is how to query it. Just define a selectors.js with the query functions you need:

export function getCounterIds( state ) {
  return Object.keys( state );
}

export function getCounterValue( state, counterId ) {
  return state[ counterId ];
}

and that’s it! Pretty obvious, right? Store selectors are functions that take (at least) one argument (the store’s state) and return a specific value. Obviously, selectors can have more arguments if you need to return a specific value from within your store.

In our case, for example, we have created two selectors:

  • getCounterIds returns an array of counter identifiers. Since we implemented the store using a dictionary/object, we’re simply interested in returning its Object.keys.
  • getCounterValue returns the specific value of a given counter. This function takes two arguments (the current state of our app and the ID of the counter we’re interested in) and returns the requested value.

How to Test Our Store

To test that the store works properly, open the src/index.js file and import it:

// Import dependencies
import { render } from '@wordpress/element';

import './store';
import { Counter } from './components/counter';

...

Then, transpile the code (using npm run build) and go to your browser. Open its Developer Tools and use the JavaScript console to type in a few commands:

dispatch = wp.data.dispatch( 'react-example/counters' );
select = wp.data.select( 'react-example/counters' );

dispatch.addCounter( 'a' );
dispatch.addCounter( 'b' );
dispatch.addCounter( 'c' );
dispatch.setCounterValue( 'a', 3 );

select.getCounterIds();
// Array(3) [ "a", "b", "c" ]

select.getCounterValue( 'a' );
// 3

Using WordPress’ dispatch and select functions you’ll be able to see that the store is working as expected. And bonus tip: there is an extension for both Firefox and Chrome called Redux DevTools that allows you to properly see your Redux stores:

Redux DevTools en Firefox
The Redux DevTools extension for Firefox and Chrome allows you to easily check the state of Redux stores.

Next Steps

Truth is, today’s post was a little bit longer than I expected, so we won’t be able to see how we can use this store to power our UI (yet). But I hope the explanation helps you understand how WordPress stores work and how you can use them to manage the state of your plugins.

Next week we will move forward with today’s example and connect our React components to the store so that (a) what we see in the UI is powered by the state stored in the store and (b) user interactions with the UI update the store (as well as the UI itself).

But, just to make sure you get comfortable with everything I’ve shown you today, let me propose some homework: modify the store we implemented today so that data is stored as follows:

const counters = [
  { id: 'ae13a', value: 0 },
  { id: 'f18bb', value: 3 },
  { id: 'e889a', value: 1 },
  { id: '8b1d3', value: -5 },
];

and does no longer us a dictionary. Keep in mind you may need to update the selectors, actions, and reducer to make it work! I’ll share a solution next week 😉

Featured Image by Annie Theby on Unsplash.

Leave a Reply

Your email address will not be published. Required fields are marked: •

I have read and agree to the Nelio Software Privacy Policy

Your personal data will be located on SiteGround and will be treated by Nelio Software with the sole purpose of publishing this comment here. The legitimation is carried out through your express consent. Contact us to access, rectify, limit, or delete your data.