Cómo escribir su primer componente React.js

React's function y class components, props, state, y event handlers

Actualización: este artículo ahora es parte de mi libro "React.js Beyond The Basics". Lee la versión actualizada de este contenido y más sobre React en jscomplete.com/react-beyond-basics .

El concepto más importante a comprender en React.js es el componente. Un componente de React puede ser de dos tipos. Puede ser un componente de función o un componente de clase . A veces escuchará términos diferentes para describir estos dos tipos, como apátrida y sin estado . Los componentes funcionales también se asocian a menudo con el concepto de presentación . Me referiré a ellos en este artículo como componentes de función y componentes de clase.

Un componente de función es la forma más simple de un componente de React. Es una función simple con un contrato simple:

El componente de función recibe un objeto de propiedades que generalmente se denomina props. Devuelve lo que parece HTML, pero en realidad es una sintaxis especial de JavaScript llamada JSX.

Un componente de clase es una forma más destacada de definir un componente de React. También actúa como una función que recibe accesorios, pero esa función también considera un estado interno privado como entrada adicional que controla el JSX devuelto.

Este estado interno privado es lo que le da a React su naturaleza reactiva . Cuando cambia el estado de un componente de la clase, React volverá a renderizar ese componente en el navegador.

Los objetos State y Props tienen una diferencia importante. Dentro de un componente de clase, el objeto State se puede cambiar mientras que el objeto Props representa valores fijos. Los componentes de la clase solo pueden cambiar su estado interno, no sus propiedades. Esta es una idea central para entender en React y este artículo tendrá un ejemplo de eso.

Veamos un ejemplo real de un componente. A muy simple, sin ninguna entrada y con un simple h1en una divsalida.

En el lado izquierdo, el componente está escrito en la sintaxis especial JSX.

JSX nos permite describir nuestras interfaces de usuario (UI) en una sintaxis muy cercana al HTML al que estamos acostumbrados. Sin embargo, es opcional. React se puede usar sin JSX, como puede ver en el lado derecho. De hecho, React simplemente compila el JSX que ve a la izquierda con el JavaScript puro que ve a la derecha. Entonces funciona con JavaScript compilado en el navegador.

La React.createElementllamada del lado derecho es una representación de JavaScript del Modelo de objetos de documento (DOM). React lo traduce de manera eficiente en operaciones DOM que realiza en el navegador.

Escribamos un componente de React.

Usaré React Playground de jsComplete para los ejemplos de este artículo. Es una herramienta en la que puede probar su código JavaScript y React directamente en el navegador. No es necesario instalar ni configurar nada.

La herramienta tiene una interfaz simple de dos paneles. El panel de la izquierda es el editor donde escribe su código JavaScript y React. La última versión de React y ReactDOM ya está precargada allí. El editor también comprende la extensión JSX y todas las funciones modernas de JavaScript. Esto nos permitirá centrarnos en la propia API de React en lugar de configurar y compilar una aplicación de React.

El panel derecho es el panel de vista previa. Tienes un mountNodeelemento predefinido en el editor. Cuando ejecuta su código JavaScript, todo lo que ingresa en el mountNodeelemento aparece en el panel de vista previa. El panel de vista previa también mostrará cualquier error que encuentre al ejecutar su código. El patio de recreo es también un REPL de JavaScript simple (Ejecutar, Evaluar, Imprimir, Loop) donde puede probar funciones y expresiones rápidas de JavaScript. Para ejecutar el código en cualquier momento presione CTRL+Enter.

Pruebe lo siguiente en REPL, por ejemplo:

mountNode.innerHTML = 'Hello!!';

O el modo REPL simple

3 == '3'

Para crear un componente de React, defina una nueva función. Hagamos que esa función devuelva un elemento de botón HTML:

function Button() { return ( Go );}

Lo que devolvimos aquí parece HTML, pero recuerde que no lo es. Se compilará en JavaScript. El JavaScript real que ve el navegador cuando usamos este elemento de botón en JSX es una llamada a la React.createElement función:

function Button() { return ( React.createElement("button", null, "Go") );}

Si bien puede usar React de esta manera sin JSX, sería mucho más difícil de codificar y mantener. Entonces, sigamos con JSX.

La función anterior es un componente React completo y muy simple. ¡Vamos a usarlo!

Usamos un componente montándolo en el navegador. La función diseñada para hacer eso es ReactDOM.render, que toma dos argumentos:

  • El primero es el componente a renderizar, en nuestro caso lo es Button.
  • El segundo argumento es el elemento en el que se debe representar este componente. En el entorno de REPL podemos usar la mountNodevariable especial .
ReactDOM.render(, mountNode);

Todos los ejemplos de código de este artículo tienen un enlace en el título de la captura de pantalla donde puede editar el ejemplo en jsComplete REPL.

Un componente de la función React recibe como primer argumento el propsobjeto. Este argumento nos permite hacer que el componente sea reutilizable. Por ejemplo, en lugar de codificar la etiqueta "Ir" del botón de arriba, podemos pasar al Buttoncomponente un labelatributo, como hacemos con los elementos HTML normales:

ReactDOM.render(, mountNode);

Luego podemos acceder a este atributo dentro del componente con un corchete para props.label.

function Button(props) { return ( {props.label} );}

El propsargumento es un objeto que contiene todos los valores que se pasaron al componente cuando se renderizó.

Hacer que el componente sea interactivo

Tenemos un elemento de botón y se renderiza a través de un componente React.

Let’s now add some interactivity to this so-far boring example. Let’s make that button element increment a counter value on every click and display that value as the button label itself. So the label of this button is going to start with the number 1 and when the user clicks the button its label will change to 2, 3, 4 and so on.

Since this is something that needs to be reflected in the component rendered output, it belongs to the state of the component. We need the component to re-render itself every time the counter changes. We cannot use a property here because a component props cannot be changed. By using the special React state object, we will be utilizing React’s reactive nature and we will not need to worry about how to take the changes to the browser. React will do that for us.

But, our Button component is currently a function component. Function components cannot have state, so we need to upgrade this component to a class component first.

This is very simple. We first define a class that extends React.Component

class Button extends React.Component { }

In that class we define a render function, which returns the component’s JSX; the HTML button in our case.

render() { return ( 1 );}

This is a little bit more code, but we can now use a private state on the Button component!

To use a state object we first need to initialize it. The state object is a simple instance property, so we can initialize it inside the constructor function of the Button class. We just define the normal constructor function (which receives a props object in React) and call the super method to honor the inheritance of the component.

constructor(props) { super(props); this.state = { counter: 1 };}

After that, we initialize this.state to whatever we want. The keys of this state object are the various elements of the state. For our case, we need a counter state, which starts from 1.

Inside the render function, since we can write any JavaScript expression within curly brackets, we can read the value of the new counter state element that we initialized on the state using this.state.counter.

render() { return ( {this.state.counter} );}

The “this” keyword refers to the component instance we are handing off to ReactDOM.

You can try and change that counter state to see how the button will render the values you put on the state.

There is another shorter syntax to define the initial state, which is to simply use a class property without a constructor call:

class Button extends React.Component { state = { counter: 1 }; render() { return ( {this.state.counter} ); }}

This is not yet part of the official JavaScript language but it will be soon. The syntax works at the jsComplele REPL playground because that tool is using Babel to transpile it to the supported JavaScript that the browser will understand.

When you configure your own React application you’ll have to use something like Babel anyway to compile JSX into JavaScript. It is an easy win to also include and use the JavaScript features that are well on their way to becoming an official part of the language.

In the Button example so far, we have a state object and an HTML button element that displays a counter value that we initialized on the state. Now we need to change that value when we click the button. We need to define a click handler on that button.

React comes with normalized events that are easy to use. For this case, we need the onClick event, which we define on the HTML button element itself:

function F() {}

Unlike DOM event handlers, which use a string, React event handlers use an actual JavaScript function. This function can be a global one (like F above), or an inline function:

 {}} />

However, the standard practice is to define a function on the class component itself. Let’s call it handleClick and we can define it on the component as an instance property:

class Button extends React.Component { state = { counter: 1 }; handleClick = () => { console.log('Button is clicked!!'); }; render() { return (  {this.state.counter}  ); }}

We are using the modern class field syntax, which allows us to use arrow functions that are bound to the component instance. handleClick will now act as a prototype function on this class. Inside handleClick the keyword “this” refers to the component instance that we are mounting in the DOM.

handleClick ’s job is easy: read the current counter value from the state object using this.state.counter. Then increment this value and update the component state with the new incremented value.

We can use React’s built-in setState method, which is available on every class component instance, to update a component state.

The button will now increment its label on every click.

This was simple and powerful! We defined an event handler for the onClick method. Every time the user clicks the button the handleClick function will be executed. The function reads the current state of the counter value, increments it, and then sets the state to the new incremented value. React takes care of all the rendering needed after these changes so you do not have to worry about that.

Note that we did not update the state object directly. We have to use React’s setState method when we want to update any element on the state. You can’t for example do this:

// WRONG:this.state.counter = this.state.counter + 1;

React’s setState method is an asynchronous one which schedules an update. Multiple setState calls might potentially be batched for performance. Since we are both reading and writing to the state object inside the handleClick function, we could hit a race condition. The general rule of thumb is whenever you need to update the state using a value from the current state, use the other contract of the setState method. This receives a function reference instead of an object as its first argument:

this.setState((prevState) => {});

Esta función recibe un prevStateobjeto que podemos usar con confianza sin preocuparnos por las condiciones de carrera. La función devuelve el objeto que queremos que React use para establecer el estado. Nuestro counterejemplo de valor anterior se convierte en:

this.setState((prevState) => ({ counter: prevState.counter + 1 }));

Solo necesita usar esta segunda sintaxis de setStatesi su actualización depende del estado actual. Sin embargo, puede ser una buena idea tener el hábito de usar siempre la segunda sintaxis de función-argumento.

Aquí está el código final:

class Button extends React.Component { state = { counter: 1 }; handleClick = () => { this.setState((prevState) => ({ counter: prevState.counter + 1 })); }; render() { return (  {this.state.counter}  ); }}
ReactDOM.render(, mountNode);

Pruébelo y si tiene alguna pregunta, hágamelo saber en los comentarios a continuación.

Este artículo es una reseña de parte de mi curso de Pluralsight - React.js: Getting Started. Allí cubro contenido similar en formato de video.

¿Aprendiendo React o Node? Revisa mis libros:

  • Aprenda React.js creando juegos
  • Node.js más allá de lo básico