Por fin llegamos a la última parte de este pequeño tutorial de introducción a React. Lógicamente he dejado muchísimas cosas en el tintero, por lo que no descarto escribir más entradas en el futuro en las que centrarnos en diferentes aspectos del stack de desarrollo JS, pero creo que con esta última entrada ya tendrás las nociones básicas necesarias para empezar a andar por tu propio pie.
La semana pasada dejamos nuestra aplicación de ejemplo en un punto muy interesante. El objetivo que nos pusimos era crear una pequeña aplicación con múltiples contadores, los cuales podríamos añadir y quitar a voluntad:

Lo último que hicimos fue dejar preparado un store que se encargaba de mantener el estado. Es decir, implementamos el esqueleto que sería capaz de sostener a nuestra aplicación. Ahora lo único que nos queda es dar cara y ojos a esta aplicación través de los componentes.
Hoy veremos cómo conectar los componentes que hemos creado en React con nuestro store de WordPress basado en Redux para que la interfaz muestre el estado que tenemos en el store y para que las interacciones con el usuario actualicen ese estado.
Solución a los deberes de la semana pasada
Antes de entrar en materia, vamos a ver la solución a los deberes que te puse la semana pasada. Si recuerdas, te dije que re-implementaras el store de tal forma que los datos de los contadores ya no se guardaran como un objeto con pares llave » valor, sino como un array de objetos, cada uno de ellos con su identificador y su valor:
const originalStore = {
x: 1,
y: 2,
};
const newStore = [
{ id: 'x', value: 1 },
{ id: 'y', value: 2 },
];
¿Qué cambios había que hacer para conseguir esta solución?
Cambios en las acciones
Las acciones son exactamente igual que las que teníamos anteriormente. Tal y como vimos la semana pasada, las acciones no son funciones que actualicen directamente el estado de tu aplicación, sino que sencillamente generan un objeto con la «petición de actualización» que quieres realizar en tu store. Así pues, actions.js
sigue igual:
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,
};
}
Cambios en el reducer
El reducer es la función que se encarga de actualizar el estado partiendo del estado anterior y aplicando los cambios pertinentes según qué acción reciba. En este caso, como queremos cambiar la estructura de datos que almacena el estado de nuestra aplicación, está claro que deberemos cambiar el 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;
}
Tal y como puedes imaginar, lo único que teníamos que hacer aquí era cambiar el valor por defecto del estado del objeto vacío {}
al array vacío []
. Luego, las diferentes acciones consisten en añadir un nuevo objeto al array, quitarlo o sustituirlo cuando recibimos un nuevo valor.

Nelio Content
Estoy tan contento con Nelio Content que parece que me hayan pagado para hablar bién de él… pero es que también a ti te encantará: funciona como prometen, la programación automática de mensajes es increíble, la calidad/precio no tiene parangón y su equipo de soporte se siente como si fueran parte del tuyo.

Panozk
Cambios en los selectores
Finalmente, tenemos el fichero con los selectores selectors.js
. Como vimos la semana pasada, los selectores reciben como parámetro el estado que tenemos en nuestra aplicación (junto con cualquier otro parámetro necesario para realizar la consulta). Por lo tanto, como hemos cambiado la representación del estado, habrá que cambiar el cuerpo de los selectores:
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;
}
En nuestro ejemplo sólo teníamos dos selectores, así que solo tenemos que reimplementar dos funciones. La primera se encarga de devolver los identificadores de todos nuestros contadores, lo cual podemos conseguir muy fácilmente con un map
. La segunda devuelve el valor del contador especificado (o undefined
si dicho contador no está), así que basta con buscar el contador cuyo id
es counterId
usando la función auxiliar de lodash find
y devolver el atributo value
del contador que hemos encontrado.
Un último apunte
La principal ventaja de usar un store es que nos permite cambiar totalmente la forma en la que organizamos los datos internamente; mientras mantengamos su interfaz (acciones y selectores), todo funcionará con normalidad. De esta forma, es posible implementar rápidamente una solución quizás no muy optimizada pero funcional y, más adelante, según crezcan nuestras necesidades, adaptarlo para mejorar su eficiencia, velocidad, uso de memoria, etc.
Re-escribiendo los componentes de nuestra interfaz
Nuestra nueva aplicación, comparada con la versión que implementamos en la parte 2, permite añadir y eliminar múltiples contadores. Teniendo en cuenta que queremos conseguir algo tal que así:

Está claro que (1) deberemos modificar cada contador para que incluya un botón de Delete y (2) la aplicación en sí deberá tener un botón para añadir nuevos contadores.
Para el primer paso, abrimos el fichero src/components/counter.js
y añadimos el nuevo botón Delete y una nueva prop
onDelete
para que, cuando pulsemos en el botón, se borre el contador:
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;
Para lo segundo, podemos crear un nuevo componente que se encargue de pintar la lista de contadores y que incluya un botón para añadir nuevos. Por ejemplo, yo te propongo crear un componente src/components/counter-list.js
tal que así:
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;
Este nuevo componente tiene varios puntos que merece la pena comentar:
- Se trata de un componente que usa otro componente. Aún no habíamos visto ningún ejemplo de ello, así que aquí tienes el primero. Lo único que necesitamos para poder usar un componente en otro componente es importarlo con
import
. - El
import
que hemos hecho no tiene llaves (import Counter
vsimport { Counter }
) porque elexport
que hemos hecho encounter.js
es undefault
. Cuando unexport
esdefault
, elimport
es sin llaves. - El componente
CounterList
recibe dos propiedades:addCounter
es la acción que nos permite añadir nuevos contadores ycounterIds
es un array con los contadores que tenemos en este momento en nuestra aplicación. - Para pintar cada contador, usamos el método
map
de un array. De esta forma, somos capaces de convertir cada «identificador» en un componenteCounter
. - En el componente
Counter
usamos dosprops
nuevas de las que aún no hemos hablado:key
ycounterId
.key
es simplemente una prop que React nos pide que usemos por cuestiones de optimización cuando hacemos elmap
de un array.counterId
es unaprop
que te explicaré un poco más adelante. - El componente
Counter
que estamos pintando no incluye ninguna de lasprops
que se supone que espera: no hayvalue
ni tampoco las accionesonIncrease
,onDecrease
uonDelete
. Esto es algo que está relacionado con el hecho de haber usadocounterId
y en seguida verás por qué lo hemos hecho así. - Al igual que
Counter
,CounterList
es unexport default
.
Re-escribiendo el fichero principal de nuestro plugin
Nuestra aplicación ya no va a mostrar un único contador, sino que tiene que mostrar una lista de contadores. Por lo tanto, debemos modificar el fichero principal index.js
que creamos en la parte 2 para que, en lugar de hacer render
de Counter
, haga el render
de CounterList
. Además, también podemos limpiar la «chapucilla» que hicimos creando una variable local para mantener el estado de nuestro plugin, porque ahora estamos usando los stores de WordPress.
Teniendo en cuenta todo esto, el nuevo fichero principal es tan sencillo como:
// 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 );
Como ves, simplemente cargamos el store y el componente CounterList
y renderizamos este último (usando la función render
del paquete @wordpress/element
) en un nodo que tenemos en preparado en el DOM. ¿El resultado? Un único botón para añadir nuevos contadores y ningún contador a la vista.
Por desgracia, el botón aún no hace nada… y esto se debe a que no hemos conectado el componente con el store.
Cómo conectar componentes React a un store Redux
Si has conseguido llegar hasta aquí, felicidades, porque estás a punto de hacer historia. Ahora mismo tenemos todos los ingredientes preparados y lo único que nos queda es juntarlos para preparar un plato delicioso. Por un lado, tenemos un store con selectores para consultar el estado de nuestra aplicación y acciones para actualizarlo. Por otro, tenemos los componentes necesarios para visualizar dicho estado. Lo único que nos falta es hacer que los componentes se nutran de la información que hay en el store…
Conectando CounterList
con el store
CounterList
es un sencillo componente que requiere dos propiedades. Por un lado, espera una lista con los identificadores de todos los componentes que tenemos activos en nuestra aplicación: counterIds
. Por otro lado, espera también un callback con el que añadir nuevos contadores: addCounter
.
Si echas un vistazo a los selectores y las acciones que definimos en nuestro store en la parte 3 del tutorial, verás que tenemos todos los ingredientes que necesitamos. Por un lado, tenemos el selector getCounterIds
, el cual nos devuelve «una lista con los identificadores de todos los componentes que tenemos». Por otro lado, tenemos la acción addCounter
que, dado un identificador, nos permite «añadir un nuevo contador».
El paquete @wordpress/data
, que ya usamos para registrar un store, ofrece un par de «componentes de alto orden» con los que vitaminar un componente existente extrayendo props
de un store: withSelect
y withDispatch
. En otras palabras, aplicando withSelect
y/o withDispatch
sobre nuestro componente, podemos pasarle las props
que queramos con valores que extraemos directamente de un store.
Como las cosas se entienden mejor con un ejemplo, veamos cómo quedaría el código en el caso de CounterList
:
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 ) );
Vamos a destripar este código punto por punto:
- Lo primero que hacemos en la versión extendida de
CounterList
es importar las nuevas dependencias que necesitaremos. En concreto, ya te he dicho que vamos a necesitarwithSelect
ywithDispatch
para conectar el store con el componente, así que ahí que van con elimport
. Luego tenemos otra dependencia: el paqueteuuid
. Se trata de un paquete que nos permite generar identificadores únicos. - El componente en sí (
CounterList
) no ha cambiado: sigue asumiendo que le van a pasar las dosprops
que necesita para funcionar y genera el HTML que ya hemos visto antes. - A continuación, usamos la función
withSelect
. Explicado someramente,withSelect
es una función que devuelve otra función que podemos aplicar a un componente para que le añada propiedades extra.- Para poder usar
withSelect
, debes pasarle una función como parámetro. En nuestro caso, hemos creado una función anónima. - La función anónima te da un parámetro,
select
, el cual te permite acceder a los selectores de los stores que tengas registrados. - Lo primero que hacemos es acceder al store que hemos creado:
react-example/counters
y, en concreto, nos quedamos con el selectorgetCounterIds
. - El resultado de esta función anónima son las
props
con datos que queremos pasarle aCounterList
. Como nos interesa pasarle una lista de identificadores llamadacounterIds
, devolemos un objeto con esaprop
y cuyo valor obtenemos al invocargetCounterIds
. - El resultado de
withSelect
lo guardamos en una variable llamadawithCounterIds
(aunque podríamos haberla llamado como quisiéramos). withCounterIds
es una función que podemos aplicar a un componente cualquiera así:withCounterIds(MiComponente)
. El resultado de hacer esto es queMiComponente
pasará a tener unaprop
llamadacounterIds
cuyo valor se ha extraído del storereact-example/counters
.
- Para poder usar
- Luego usamos la función
withDispatch
. Su funcionamiento es exactamente el mismo quewithSelect
, pero en lugar de darnos acceso a los selectores de un store, nos da acceso a las acciones.withDispatch
, igual quewithSelect
, espera una función como parámetro.- Esta función tiene como primer parámetro
dispatch
, quien nos da acceso a las acciones de los stores que tengamos disponibles. - Usando
dispatch
, obtenemos la acciónaddCounter
de nuestro store:react-example/counters
. - La función
addCounter
que espera nuestro componente es una función sin parámetros, pero la acciónaddCounter
que tenemos en nuestro store sí espera un identificador único como parámetro. Por lo tanto, está claro que no podemos usar directamente la acción del store en nuestro componente… La solución es sencilla: basta con que eladdCounter
que pasaremos a nuestro componente sea una función anónima que internamente use la acciónaddCounter
del store pasándole ese identificador único, el cual podemos generar al momento usando el paqueteuuid
. withCounterAdder
es otra función que podemos aplicar a un componente cualquiera (de forma análoga awithCounterIds
) cuyo efecto es añadir a dicho componente laprop
addCounter
que acabamos de describir.
- Finalmente, extendemos nuestro componente
CounterList
aplicándole, primero, la funciónwithCounterIds
para queCounterList
tenga laprop
counterIds
con los valores que hay en el store y, al resultado de esto, le aplicamoswithCounterAdder
para que también incluya laprop
addCounter
.
Y, con esto, ¡ya puedes añadir nuevos contadores! Ahora solo nos queda conectar un contador cualquiera con el store…
Conectando cada Counter
con el store
Veamos, ahora, los cambios que tenemos que hacer en el componente Counter
para conseguir conectarlo con nuestro store. Básicamente, lo único que tenemos que hacer es repetir el proceso que acabamos de ver para CounterList
, pero usando los selectores y las acciones de react-example/counters
que nos interesa.
De nuevo, vamos a empezar viendo el código y luego explicamos sus particularidades:
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 );
- Como siempre, empezamos haciendo los
import
de rigor que vamos a usar en este componente. - El componente en sí,
Counter
, es el mismo que teníamos antes, con susprops
:value
,onDelete
,onIncrease
yonDecrease
. - Lo primero que hacemos es usar
withSelect
para recuperar el valor del contador que tenemos en el store.- Nuestro store mantiene el estado de muchos contadores, así que tenemos que indicarle exactamente cuál es el contador que nos interesa usando su identificador. Por suerte, cuando
CounterList
pintaba cada uno de losCounter
usando la funciónmap
, les pasaba una propiedadcounterId
, ¿recuerdas? Pues la función anónima que le pasamos awithSelect
tiene como segundo parámetro lasprops
del componente, con lo que tenemos acceso acounterId
. - El cuerpo de esta función anónima es muy similar al ejemplo que hemos visto con
CounterList
. Simplemente accedemos al selectorgetCounterValue
y devolvemos como resultado el valor concretovalue
que tiene el selector con identificadorcounterId
.
- Nuestro store mantiene el estado de muchos contadores, así que tenemos que indicarle exactamente cuál es el contador que nos interesa usando su identificador. Por suerte, cuando
- Las acciones que necesita
Counter
las tenemos que obtener a través dewithDispatch
.- En nuestro store, sabemos que disponemos de las acciones
setCounterValue
yremoveCounter
. - Ambas acciones necesitan el identificador del contador a actualizar o borrar, el cual, tal y como acabamos de ver, tenemos disponible en las
props
de entrada que están en el segundo parámetro de la función anónima. - La acción
setCounterValue
necesita, además, conocer el nuevo valor a meterle al contador. Como las funciones deCounter
son incrementar o decrementar en uno el valor actual, necesitamos conocer dicho valor. Por suerte, este lo acabamos de obtener con elwithSelect
y está en laprop
llamadavalue
. - El resultado de aplicar
withDispatch
son las tres funciones que espera nuestro componente:onIncrease
es una función anónima que suma1
alvalue
actual decounterId
;onDecrease
es otra función anónima que hace lo mismo, pero restando1
; yonDelete
es una función que usa la acciónremoveCounter
con el identificador que toca.
- En nuestro store, sabemos que disponemos de las acciones
- Finalmente, aplicamos ambas funciones
withValue
ywithActions
a nuestro componenteCounter
. No obstante, esta vez, en lugar de primero aplicar una función y luego otra sobre el resultado de la primera, he decidido usar la función auxiliarcompose
del paquete@wordpress/compose
, ya que permite escribir un código un poco más inteligible.
¡Y esto es todo! Ya deberías tener la aplicación funcionando correctamente 🙂
En resumen
A lo largo de esta pequeña introducción a React/Redux en WordPress hemos visto todos los ingredientes necesarios para crear buenas interfaces de usuario usando esta tecnología. En esencia, hemos visto que los componentes deben ser simples funciones que reciben propiedades y generan HTML, hemos visto cómo podemos usar los stores para mantener el estado de la aplicación de forma independiente a los componentes de la UI y hemos visto cómo podemos conectar ambas partes.
Conseguir que un componente obtenga los valores que necesita de un store o permitir que un componente pueda modificar el estado de un store es tan fácil como conectarlo al mismo usando las funciones withSelect
y withDispatch
del paquete @wordpress/data
.
Imagen destacada de Dmitry Nucky Thompson en Unsplash.
Deja una respuesta