Welcome back to our introduction to React, where we teach you the basics so you understand how this technology works and thus write better code and create better user interfaces. In the previous post, we talked about how you could create React components as simple pure functions. The mantra that I repeated over and over was that «a React component is a simple function that gets a set of properties (props
) and generates some HTML».
Last time we talked about React we ended with the following question: if a component is such a simple function, how can we do anything useful with it? Well, today we will talk about the dynamic part of React: that is, how we can make a component react to user events.
Setting Up the Environment
The first part of this tutorial had some theory but didn’t include any examples, and I apologize for this; I think the best way to learn is by doing, so let’s fix this! From now on, everything I’ll teach you will be based on an example that we’ll work on, so let’s set up the environment that we’ll use first.
I’m going to assume you have a WordPress site you can work on, as well as the dev tools node
and npm
. If that’s not the case, in this post I explained how you can use Lando to create a local WordPress installation, and npm
‘s documentation has everything you need to know to install node
and npm
.
First, let’s create a simple WordPress plugin where we’ll have our React components. To do this, create a folder called react-example
in wp-content/plugins
and put the react-example.php
file inside with the following content:
<?php
/**
* Plugin Name: React Example
* Description: Example.
* Version: 1.0.0
*/
namespace React_Example;
defined( 'ABSPATH' ) or die();
function add_page() {
add_menu_page(
'React Example',
'React Example',
'read',
'react-example',
function () {
echo '<div id="react-example-wrapper"></div>';
}
);
}
add_action( 'admin_menu', __NAMESPACE__ . '\add_page' );
function enqueue_scripts() {
$screen = get_current_screen();
if ( 'toplevel_page_react-example' !== $screen->id ) {
return;
}
$dir = untrailingslashit( plugin_dir_path( __FILE__ ) );
$url = untrailingslashit( plugin_dir_url( __FILE__ ) );
if ( ! file_exists( "{$dir}/build/index.asset.php" ) ) {
return;
}
$asset = include "{$dir}/build/index.asset.php";
wp_enqueue_script(
'react-example',
"{$url}/build/index.js",
$asset['dependencies'],
$asset['version'],
true
);
}
add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts' );
The plugin simply adds a new page on your WordPress Dashboard called React Example and loads a JavaScript file in it. For now, the result should be a blank page:

which shouldn’t come as a surprise, given that we haven’t implemented anything in JavaScript yet.
As we saw in this previous post on how to add a button to Gutenberg, you must run the following commands in your terminal (in the plugin folder we are creating):
npm init
npm install --save-dev @wordpress/scripts
if you want to be able to write React components and transpile JavaScript, just follow the instructions on the screen. Oh, and don’t forget to update your package.json
file so that it included all the relevant scripts
required by @wordpress/scripts
.
Finally, create a src
folder with the following index.js
file:
// Import dependencies
import { render } from '@wordpress/element';
// Create component
const HelloWorld = () => <div>Hello World</div>;
// Render component in DOM
const wrapper = document.getElementById( 'react-example-wrapper' );
render( <HelloWorld />, wrapper );
Run npm run build
(or npm run start
if you want the code to be transpiled automatically every time you change your source files), refresh the page and, voilà, you have everything ready:

Creating a Reactive Functional Component in React
Let’s now create a component that reacts to user events. The example I want us to implement together is this one:

a simple counter that allows you to increment or decrement its value by one using the + and - buttons respectively. I know it’s extremely simple, but I hope it’ll help you understand the difference between the “data model” and the “user interface,” as well as guide you through the implementation of dynamic components.
Better Code Organization
Let’s start by improving the organization of our code. Today’s example is so simple that you might want to skip this step, but I think it’s helpful to be organized from the very beginning.
In src
, create a new foldercomponents
that will contain all the components used by our app. Since today’s example only has one single component, the Counter, all we need to do is create a single file, counter.js
:
export function Counter() => (
<p>Counter</p>
);
which, as you can see, simply exports the function in charge of rendering our component. Then, modify src/index.js
so that instead of rendering our sample HelloWorld, it imports and uses our new Counter component:
// Import dependencies
import { render } from '@wordpress/element';
import { Counter } from './components/counter';
// Render component in DOM
const wrapper = document.getElementById( 'react-example-wrapper' );
render( <Counter />, wrapper );
And that’s it!

Nelio Forms
A fantastic contact plugin using the block editor. In its simplicity lies the true power of this plugin. I love it, very versatile and it worked perfectly for me.

Wajari Velasquez
Implementing a Counter Component
Today’s goal is to implement a component that keeps track of a the value of a counter, which we can increase or decrease by clicking on a couple of buttons. This means our component must have three things:
- An element to display the current value
- A + button to increase said value
- A - button to decrease it
Implementing this component is quite straightforward:
export const Counter = ( { value } ) => (
<div>
<div>Counter: <strong>{ value }</strong></div>
<button>+</button>
<button>-</button>
</div>
);
since you know that your component takes a value
as one of its props and has to include two buttons. Unfortunately, you may not know what to do next if you want those buttons to modify the value, specially if we take into account that, last time we talked about this, I told you “components can’t change their properties.”
Functions as Props and Separation of Responsibility
Remember: a React component is a function that receives properties and generates HTML. This means that, whoever invokes this function (or, in other words, whoever uses this component) must give it all the parameters (properties) that it needs.
Your intuition already told you that the value of the counter is one of the properties it needs. We don’t know where this value comes from (and we don’t care), but we know our component will be given this value. You also know that our Counter component must be able to modify this value somehow. In other words, the component should be such that, when the user clicks + or -, the value should be updated.
If you think for a moment about this statement, you’ll quickly realize that the Counter component does not only expect a value
(of type number), but it also needs two additional props of type function: one that increments said value, and another one that decrements it. In other words, Counter needs three different props
:
value
: it’s a number and the counter’s value (duh!)onIncrease
: it’s a function that, when invoked, increases the counter’s valueonDecrease
: it’s another function that, when invoked, decreases the counter’s value
If we work under the assumption that our component will be given these three props, the component itself becomes extremely simple:
export const Counter = ( { value, onIncrease, onDecrease } ) => (
<div>
<div>Counter: <strong>{ value }</strong></div>
<button onClick={ onIncrease }>+</button>
<button onClick={ onDecrease }>-</button>
</div>
);
Notice how our component is not executing the functions it receives (which, as we’ve already said, is “forbiddden” as it’d trigger a side effect). All it does is connect them to the click
event of our buttons. This means we’ve successfully implemented a pure function (as all components should be) because, given the same three props, the result will always be the same HTML with the same “connections.”
Unfortunately, if you try to use the component, you’ll see nothing works 🙁
Giving the Component ALL the props it needs
And no wonder it doesn’t work! When we used this component, we didn’t honour it’s contract and we didn’t add the props it requires to properly work. Just look at index.js
:
render( <Counter />, wrapper );
The counter doesn’t have a value
nor does it have the onIncrease
or onDecrease
functions!
Well, let’s fix this one quickly. All we have to do is create a variable that stores the value of the counter and implement a function that updates it, so that we can use them in our component:
// 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
);
Thanks to this architecture, we managed to completely separate the user interface (which is the component) from data management. The component is completely agnostic about who or how the value is stored; all it cares about is that it gets the props it needs.
If, once again, we refresh the page, you will see that now the counter does show a default counter value (i.e. 0
), but clicking on + and - doesn’t work yet…
Re-rendering the Component on Update
If you look at the previous snippet, you might think that things should be working now. But clearly they don’t… so what’s amiss? Well, let’s run a small test. Modify the setValue
function so that, in addition to assigning the new value to value
, it also logs it in the console:
function setValue( newValue ) {
value = newValue;
console.log( 'Value is', value );
}
and check again if things work as expected. Just open your browser’s developer tools and click on your buttons:

Interesting, right? It looks like value
is properly updated every time the user clicks on a button, but the component never updates.
If you want to render a component, you need to invoke its pure function with the required props. When a prop changes, you need to invoke the pure function again with the new value, so that the new HTML can be generated. So, if we want our UI to show the latest value
, we need to manually re-render the component every time value
is updated:
// Import dependencies
import { render } from '@wordpress/element';
import { Counter } from './components/counter';
// Store value
let value = 0;
function setValue( newValue ) {
value = newValue;
refreshComponent();
}
// Render component in DOM
const wrapper = document.getElementById( 'react-example-wrapper' );
function refreshComponent() {
render(
<Counter
value={ value }
onIncrease={ () => setValue( value + 1 ) }
onDecrease={ () => setValue( value - 1 ) }
/>,
wrapper
);
}
refreshComponent();
As you can see, we’ve simply created a new function named refreshComponent
that we use to render the component the first time and every time the value
is updated via setValue
. This results in the expected behavior:

Really? Rendering Components Looks Like Crap!
If you’ve come this far by following today’s tutorial, you’ve probably been a bit disappointed with the way we re-render React components. And I don’t blame you—the last section of this tutorial was indeed quite crappy. But that’s because it was just a simple example to show you how you can separate “data” from “UI.”
Today you have learned two very important lessons :
- The props a component might need are not only “data” (such as numbers, strings, arrays, objects…) but they can also be “functions.” This means we can connect these functions to DOM events so that they run “automagically” when the user performs certain actions and thus update the state of our app.
- The components (UI) and the data we use are completely independent of each other. A React component doesn’t care who or how the props it needs are managed. This means we can implement a crappy solution to manage and update the
value
of our counter and things will “just work.”
In the next post we will improve this solution and use WordPress stores (which are based on Redux) to manage the app state. Do not miss it!
Featured image of Hermes Rivera on Unsplash.
Leave a Reply