Introduction to React, part 4

Published in WordPress.

Watch our video

There is a better version of your web

Share this post

We finally come to the last part of this small introduction to React. Logically I have left many things in the pipeline, so I do not rule out writing more posts in the future where we’ll be focusing on different aspects of the JS development stack, but I think that with this last post you will already have the basic notions necessary to grow and get better on your own.

Last week we ended up things at a very interesting point. Our goal was to create a small application with multiple counters, which you could add or remove at will:

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

By the time we ended last post we had an amazing store to keep track of our application’s state. This means we already have all the basics we need to finally build the UI.

Today we will see how to connect React components with our Redux-based WordPress store so that the interface shows the state we have in the store and so that user interactions update said state.

Solution to Last Week’s Homework

But before we do so, let’s quickly review the homework I left you with last week. Essentially, I asked you to re-implement the store so that it no longer uses a dictionary, but an array of objects:

const originalStore = {
  x: 1,
  y: 2,
};

const newStore = [
  { id: 'x', value: 1 },
  { id: 'y', value: 2 },
];

What changes did you do to apply this update to your store? Well, let’s review them all quickly!

Actions

Store actions are exactly the same as those we already had, so I hope you didn’t change anything here, as it wasn’t needed. As you saw last week, actions don’t update (nor access) the store directly, and therefore any changes on the underlying structure of our store won’t have any effect here.

Action are simple functions that generate an object that signals an “update request,” and that’s precisely what we still have in our actions.js file:

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,
  };
}

Reducer

The reducer is the function responsible for updating the state based on the previous state and a dispatched action. In this case, since we want to change the data structure that stores the state of our application, we do need to re-implement the reducer:

import { map, find, without } from 'lodash';

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

    case 'REMOVE_COUNTER':
      return without(
         state,
         find( state, { id: action.counterId } )
      );

    case 'SET_COUNTER_VALUE':
      return map(
        state,
        ( counter ) =>
          action.id === action.counterId
            ? { ...counter, value: action.value }
            : counter
      );
  }

  return state;
}

As you can imagine, all we had to do here was to change the default value of the state from the empty object {} to an empty array []. Then, we simply need to set each case in our switch block so that actions update the new store correctly. Essentially, this is adding, removing, or updating objects in the array.

Selectors

Finally, we have our selectors.js file. As we saw last week, a selector receives the current state of our application and any other parameters we might need, and retrieves the requested value from this state. Since we changed how the state is stored, we also need to change the body of our selectors:

import { map, find } from 'lodash';

export function getCounterIds( state ) {
  return map( state, 'id' );
}

export function getCounterValue( state, counterId ) {
  const counter = find( state, { id: counterId } ) || {};
  return counter.value;
}

Again, it’s no big deal… but I hope you got this one right 🙂

In our example we only had two selectors, so we only have to reimplement two functions. The first is responsible for returning the identifiers of all our counters, which we can very easily achieve with map. The second returns the value of the specified counter (or undefined if the given counterId can’t be found in our store), so all you have to do is search for the counter whose id is counterId using Lodash’s helper function find and return the value attribute of the related counter (if any).

One Last Note

The reason I asked you to tweak your store was so that you could learn the following lesson: the main advantage of using stores like this one is that they behave like black boxes—you can completely change how it’s internally organized and everything will work as expected, as long as its interface (i.e. actions and selectors) don’t change.

Re-Writing the Components of Our UI

We want our users to be able to add and remove counters at their will:

Multiple counters with React and Redux
The application we want to build today.

and manage each counter independently from each other. What changes do we need to apply to what we had in the UI we implemented in part 2? Well, we obviously need to (1) modify each counter to include a Delete button and (2) tweak the app so that there’s a new button to add new counters. So let’s do this!

First, open the file src/components/counter.js and add the new Delete button and a new prop onDelete so that, when the user clicks on the button, the counter is deleted:

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

export default Counter;

Next, create a new component that will be responsible for rendering (a) all the counters the user added and (b) the button for adding new ones. I recommend creating a src/components/counter-list.js file as follows:

import Counter from './counter';

const CounterList = ( { addCounter, counterIds } ) => (
  <div>
    { counterIds.map( ( id ) => (
      <Counter key={ id } counterId={ id } />
    ) ) }
    <button onClick={ addCounter }>Add Counter</button>
  </div>
);

export default CounterList;

This new component has a few interesting things worth mentioning:

  • It’s a component (CounterList) that uses another component (Counter). We didn’t see any examples of this in our tutorial yet so… now you have one! The only thing you need to remember is that you’ll have to import all the components you want to use.
  • Our import statement doesn’t have curly braces (import Counter vs import {Counter}), even though all the imports we used so far did. That’s because Counter is a default export now, and default exports aren’t imported with curly braces.
  • The CounterList component receives two properties: addCounter is the action that allows us to add new counters and counterIds is an array with the identifiers of all the counters our app has.
  • To render each counter, we map the array of IDs to Counter instances.
  • In this map, the Counter component has two new properties: key and counterId. key is a prop required by React to optimize its rendering engine. counterId is something that will discuss later on.
  • The Counter component doesn’t include any of the props it expects. That’s because we’ll populate them using the store (and the new prop counterId will help us with that).
  • Like Counter, CounterList is an export default.

Main File

Since our app does no longer show a single counter, but a list of counters, we need to tweak index.js so that it renders the new component we just created: CounterList. Moreover, we can also get rid of all the code we wrote in part 2 to manage the state of our single counter, because we now have a proper WordPress store.

Taking all this into account, this is how index.js should look like:

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

import './store';
import CounterList from './components/counter-list';

// Render component in DOM
const wrapper = document.getElementById( 'react-example-wrapper' );
render( <CounterList />, wrapper );

As you can see, we simply import our store and the CounterList component and we render the latter (using the render function of the @wordpress/element package) in a DOM node.

Unfortunately, nothing works as expected (yet), but that’s because we haven’t connected any of our components to the store.

How to Connect React Components to a Redux Store

Cool! Everything we need is ready and we’re just one step away from our goal. On the one hand, we have a store with selectors to query the state of our application and actions to update it. On the other hand, we have the necessary components to visualize this state in the UI. The only thing that’s missing is glueing the two pieces together…

Connecting CounterList to the Store

CounterList is a simple component that requires two properties. On the one hand, it expects a list with the identifiers of all the components that we have active in our application: counterIds. On the other hand, it also expects a callback with which to add new counters: addCounter.

If you take a look at the selectors and the actions we defined in our store in part 3 of the tutorial, you will see we do have such a selector and action. The store does indeed have the getCounterIds selector, which returns “a list with the identifiers of all the components we have.” It also has the addCounter action, which adds a new counter in our app (provided you give it a unique identifier). So let’s see how we can use those in our component.

The @wordpress/data package, which we already use to register the store, offers a couple of high-order components to extend an existing component with properties derived from a store: withSelect and withDispatch. This means that, by applying withSelect and/or withDispatch on a component, we can augment said component with the props we want using values defined in a store.

Let’s see this with an example:

import { withSelect, withDispatch } from '@wordpress/data';

import { v4 as uuid } from 'uuid';

import Counter from './counter';

const CounterList = ( { addCounter, counterIds } ) => (
  <div>
    { counterIds.map( ( id ) => (
      <Counter key={ id } counterId={ id } />
    ) ) }
    <button onClick={ addCounter }>Add Counter</button>
  </div>
);

const withCounterIds = withSelect( ( select ) => {
  const { getCounterIds } = select( 'react-example/counters' );
  return {
    counterIds: getCounterIds(),
  };
} );

const withCounterAdder = withDispatch( ( dispatch ) => {
  const { addCounter } = dispatch( 'react-example/counters' );
  return {
    addCounter: () => addCounter( uuid() ),
  };
} );

export default withCounterAdder( withCounterIds( CounterList ) );

I know there’s a lot of things going on here, so let’s take it one step at a time:

  • The first thing we do in the extended version of CounterList is import the new dependencies we need. Specifically, we import withSelect and withDispatch. Then, we have a weird import: uuid. This is a package that generates unique random identifiers, and you’ll see why we need it in a bit.
  • The component itself (CounterList) didn’t change. It still assumes it’ll get the two props it needs to render the UI: a list of counter IDs and an action to add new counters.
  • Next, we use withSelect. withSelect is a higher-order component, but you can think of it as “a function that returns another function.” The resulting function is something that will augment the capabilities of an existing component (keep reading and it’ll all make sense soon).
    • In order to use withSelect, you must first call it with a function as a parameter. In our case, we have created an anonymous function.
    • The anonymous function has an argument, select, that lets you access all the selectors registered in your stores.
    • The first thing we do in this anonymous function is retrieve the getCounterIds selector from our react-example/counters store.
    • The result of this anonymous function are the props we want to add to our CounterList component. In this case, it’s simply the list of identifiers counterIds, whose values were retrieved by getCounterIds.
    • We save the result of withSelect (remember, this result is a higher-order component, or a new function if you will) in a variable called withCounterIds. We can now apply this higher-order component/function to any component we like as follows: withCounterIds(MyComponent). The result of doing so is that MyComponent will now have the property counterIds properly set with a value retrieved from the react-example/counters store.
  • Then we use the withDispatch higher-order component/function. Its operation is exactly the same as withSelect, but instead of giving us access to store selectors, it gives us access to its actions.
    • withDispatch, like withSelect, expects a function as a parameter.
    • Its first parameter is now called dispatch. We use it to access store actions.
    • Using dispatch, we get the addCounter action from our react-example/counters store.
    • The addCounter function our component expects is a function with no arguments. The addCounter action from our store, however, does need an argument: the unique identifier the new counter should have. To solve this mismatch, all we need to do is return an anonymous function that matches the prop our CounterList components expect (i.e. a function with no arguments) whose execution will call the actual action in the store. Since the action in the store needs a unique identifier, we use the package uuid to generate a unique ID every time we call the prop addCounter.
    • withCounterAdder is the result of this withDispatch, and is analogous to withCounterIds. Applying this higher-order component to an existing component will provide the existing component with an addCounter prop, which is exactly what we wanted.
  • Finally, we extend our CounterList component by first applying the withCounterIds higher-order component (so that CounterList receives a list of counterIds) and then we apply withCounterAdder (so that it receives the addCounter prop).

And that’s it! You’ve now successfully connected your first component with a store, which means you should now be able to add counters (and see them) in your UI.

Connecting each Counter to the Store

Now let’s tweak our Counter component so that it gets the props it needs from the store. As you can imagine, all we need to do is repeat the process we just did: use a few selectors and actions from react-example/counters via withSelect and withDispatch and apply the resulting higher-order components to Counter.

Let’s start by looking at the final source code:

import { compose } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';

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

const withValue = withSelect( ( select, { counterId } ) => {
  const { getCounterValue } = select( 'react-example/counters' );
  return {
    value: getCounterValue( counterId ),
  };
} );

const withActions = withDispatch( ( dispatch, { counterId, value } ) => {
  const {
    setCounterValue,
    removeCounter,
  } = dispatch( 'react-example/counters' );
  return {
    onIncrease: () => setCounterValue( counterId, value + 1 ),
    onDecrease: () => setCounterValue( counterId, value - 1 ),
    onDelete: () => removeCounter( counterId ),
  };
} );

export default compose( withValue, withActions )( Counter );
  • First of all, we import the dependencies we’ll need.
  • The component itself, Counter, doesn’t change either. It still expects four props: value, onDelete, onIncrease, and onDecrease, which we now know will get from the store.
  • First, usewithSelect to retrieve the value of the counter from our store.
    • The store maintains the values of many counters, so we have to tell it exactly which counter we want. Luckily, when CounterList mapped each counter ID to a component, we rendered each Counter with a counterId prop, remember? Well, that’s how we can tell our store the exact value we’re interested in…
    • The body of this anonymous function is very similar to the example we have seen with CounterList. We simply get the getCounterValue selector from our store and return the correct value using the counterId prop. Notice that the anonymous function has now a second argument: the list of props of the component.
  • Next, we use withDispatch to populate the actions our Counter needs.
    • In our store, we know that we have the setCounterValue and removeCounter actions.
    • Both actions need the identifier of the counter to update or delete, which, as we have just seen, is available in the second argument of the anonymous function we defined in withDispatch.
    • setCounterValue also needs to know the new value we want to set. Since our Counter component needs to functions (one to increase and another one to decrease its current value), we also need to know what the current value is so that we can add (or substract) one. Luckily, we just retrieved the current value of the counter using withSelect, which means there’s a prop named value.
    • The result of applying withDispatch is a higher-order component that will have the three functions our component expects: onIncrease is an anonymous function that adds 1 to the current value of counterId; onDecrease is another anonymous function that does the same thing, but subtracting 1; and onDelete is a function that uses the removeCounter action from the store.
  • Finally, we apply the higher-order components we’ve just created (withValue and withActions) to our Counter component. This time I’ve used the compose helper function provided by @wordpress/compose, but please notice it’s exactly the same thing we did before with CounterList.

And that’s it! You now have a working example with several components that react to and modify a store!

In Summary

Throughout this short introduction to React/Redux in WordPress we’ve seen all the ingredients necessary to create good UIs. In essence, we have seen that components must be pure functions that receive properties and generate HTML, how we can use stores to maintain the state of our app independently from the UI, and how we can integrate each other.

Extending React components so that they pull values from a store is as easy as using withSelect and withDispatch and generate a higher-order component that adds the required missing props.

Featured Image by Dmitry Nucky Thompson 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.