react revisitado: 05. Eventos

Cómo funcionan los eventos de React mediante handling events.

George Saru

archivado en: JavaScript / 17 Mayo, 2017 / taller:

Al igual que sucede con otras características relacionadas con JSX, la manera de trabajar con eventos en React es poco intuitiva y más verbosa que otros frames como vue o angular, por lo que antes de entrar en materia recordaré cómo funcionan en general los «handling events», una expresión que podemos traducir como manejadores o controladores de eventos.

En javaScript se pueden definir los eventos de unas tres formas distintas:

1. Mediante un atributo indicado en el tag.

<button onclick="foo()" id="myButton">Bazinga!</button>

2. Definiéndolo directamente entre las propiedades del nodo.

document.getElementById('myButton').onclick = foo.

3. Añadiendo un listener.

document.getElementById('myButton').addEventListener('click', foo, true);

Sin embargo, en general, con los frameworks y librerías no se manejan estos eventos directamente, sino mediante un agente intermediario, un event handler. Esto es por ejemplo lo que hacemos en jQuery con la fórmula:

$('myButton').click(function() {

// do stuff

});

En la que le pasamos al manejador de eventos click() una función para que se ejecute cuando se clickea en el nodo seleccionado.

En React sucede lo mismo, aunque se puedan engarzar listeners, la manera natural de definir los eventos son mediante event handlers que se escriben en el tag de JSX, con camelCase, seguidos de la función que deben lanzar cuando se produce el evento que se ha definido. Por ejemplo, así lanzaríamos un click:

<span onClick={funcionAEjecutar}>foo</span>

Pero, además de referenciar la función, sin paréntesis, por el comportamiento peculiar del this en javaScript, hay que recurrir al bind(), un método que crea una nueva función idéntica a la original. Aunque esto se puede hacer al vuelo en el render, la fórmula más elegante y efectiva es en el constructor.

class MyComponent extends Component {

constructor(props) {

super(props);

this.awesomeMethod = this.awesomeMethod.bind(this);

}

awesomeMethod() {

// hacemos algo

}

render() {

return (

<button onClick={this.awesomeMethod}>Hacer algo</button>

);

}

}

Como no se pueden indicar añadir los paréntesis cuando referenciamos el método en el render, pues en ese caso se ejecutaría en el momento y no cuando se produjera el evento, para pasarle valores al método indicado en el render hay que hacer un apaño y es pasarle al handler una función que retorna la función que queremos ejecutar. Por fortuna, con las arrow function esto no queda muy farragoso, aunque es menos elegante que los eventos de angular, polymer o vue.

<button onClick={ ()=> this.awesomeMethod('uka lele') }>Hacer algo</button>

Antes de pasar a la práctica, indicar por último que estos manejadores de eventos reactianos envían por defecto una especie de evento especial denominado SyntheticEvent, que es un wraper del evento nativo.

A efectos prácticos, funciona igual que los normales:

awesomeMethod(event) {

event.preventDefault();

// hacemos algo

}

Y cuentan con casi todos los métodos y propiedades de los eventos nativos y alguno más, pero, además, si necesitáramos alguno que no viniera, podríamos recuperar el original sin problemas.

event.nativeEvent.offsetX

Veamos todo esto en la práctica.

Un toggle

En nuestra aplicación, ahora estamos mostrando al mismo tiempo los gastos y los ingresos, pero es de suponer que con el tiempo estos datos vayan creciendo y a veces nos incordie tener un tipo en pantalla, así que vamos a crear sendos botones que oculten el bloque respectivo.

Lo primero es añadir en el estado dos propiedades que nos servirán de flag para saber si debemos mostrar o no el bloque correspondiente.

view-main/view-main.js

class ViewMain extends Component {

constructor(props) {

super(props);

this.state = {

showInputs: true,

showOutputs: true

....

Luego, en el constructor, bindeamos consigo mismo el método que servirá para ocultar o mostrar un bloque. Por convención, podemos comenzar llamándolo handleNombreEvento...

view-main/view-main.js

...

this.handleClickHideBlock = this.handleClickHideBlock.bind(this);

...

Este método es muy sencillo. Recibe por parámetro el nombre del bloque que debe cambiar -showInputs o showOutputs- y le asigna, siempre mediante setState, el calor contrario al que ya tenía.

view-main/view-main.js

handleClickHideBlock(block) {

this.setState({[block]: !this.state[block]});

}

Ahora solo nos falta retocar la vista. Quitamos del subcomponente ListEconomy el titular, ya que lo resituaremos arriba.

view-main/list-economy.js

const ListEconomy = ({subtitle, items, subtotal}) => {

return (

<article>

<h5>{ subtitle }</h5>

....

ListEconomy.propTypes = {

subtitle: PropTypes.string.isRequired,

...

Y luego en el componente principal colocamos el titular en cada uno de los bloques y le añadimos un botón desde el que lanzaremos nuestro handler. Además, mostraremos un signo + o - según esté o no abierto el bloque. Por último, englobamos cada bloque dentro de una estructura condicional para que se renderice o ese fragmento según el estado de cada momento.

view-main/view-main.js

...

<MainHeader />

{/* Ingresos */}

<h4>

<button onClick={ ()=> this.handleClickHideBlock('showInputs') } >

{ this.state.showInputs ? '-' : '+' } Ingresos

</button>

</h4>

{ this.state.showInputs &&

<ListEconomy subtitle='Ingresos' items={this.state.inputs} subtotal={this.state.subtotalInputs}

/>

}

{/* Gastos */}

<h4>

<button onClick={ ()=> this.handleClickHideBlock('showOutputs') } >

{ this.state.showOutputs ? '-' : '+' } Gastos

</button>

</h4>

...

Eventos callback

Ahora que sabemos cómo funcionan los eventos podemos hacer otra cosa chula y es añadir un span al listado de gastos que al ser pulsado elimine esa entrada del registro. Podría tentarnos resolverlo igual que antes, ocultando el ítem pulsado, pero esta solución sería un antipattern ya que las propiedades nunca se deben cambiar desde el componente que las recibe. Dicho de otra forma, cada componente puede modificar su propio estado, pero nunca las propiedades que recibe de un componente superior.

Por lo tanto, tenemos que buscar otra solución y es pasarle por callback al subcomponente la función que se ejecutará en el componente padre, donde sí que podemos modificar el estado.

Repetimos la jugada anterior y en el constructor bindeamos este nuevo método.

this.handleClickDeleteOutput = this.handleClickDeleteOutput.bind(this);

Definimos el método, que recibe por parámetro el id del ítem a borrar.

handleClickDeleteOutput(idOutput) {

let arrTemp = this.state.outputs.filter( (item)=> {

if ( item.id !== idOutput ) {

return true;

}

return false;

});

this.setState({outputs: arrTemp});

}

Se lo pasamos al componente ListEconomy cuando son los gastos.

<ListEconomy deleteItem={ this.handleClickDeleteOutput } items={this.state.outputs} subtotal={this.state.subtotalOutputs} />

Y ya en este componente, en el caso de que reciba esta propiedad, renderizamos el span con el evento.

const ListEconomy = ({items, subtotal, deleteItem}) => {

return (

<article>

<ul>

{ items.map((item) =>

<li key={item.id}>

{item.description}: {item.quantity}

{ deleteItem &&

<span onClick={()=> deleteItem(item.id)}> x</span>

}

</li>

) }

</ul>

...

Bueno, pues por hoy lo dejo aquí. Dejo en git una versión de la demo tal y como está en este punto.

|| Tags: ,

Este artículo aún no ha sido valorado.

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

Aportar un comentario

*