Software Crafters® 2025 | Creado con 🖤 para elevar el nivel de la conversación sobre programación en español | Legal
Hoy me toca escribir sobre otra de las tecnologías que uso en varios de los proyectos en los que trabajo, ReactJS. Se que existe muchísima información en la red sobre esta librería, aunque en muchos casos inconexa.
El objetivo de este artículo es tratar de organizar y condensar los conceptos fundamentales para comenzar a utilizar esta tecnología desarrollando un ejemplo práctico (demo).
Como muchos ya sabréis, ReactJS es una librería Javascript desarrollada por Facebook y diseñada para ayudarnos a crear SPA's (Single Page Application), su objetivo concretamente es tratar de facilitar la tarea de desarrollar interfaces de usuario. Podríamos decir que React es la V en un contexto en el que se use el patrón MVC o MVVM.
Hace uso del paradigma denominado programación orientada a componentes. Dichos componentes se representan como clases que heredan de la clase
Component
cuyo único requerimiento especial es especificar el método render que define cuál será el contenido del mismo:
class MyComponent extends React.Component { render() { return ( <h1>Hello World</h1> ); } }
La definición de dichos componentes se realiza usando una sintaxis especial llamada JSX que permite escribir etiquetas HTML dentro de JavaScript para mejorar la expresividad del código. Usar JSX no es obligatorio, pero si es muy recomendable. Para más información sobre JSX puedes consultar la documentación oficial.
Normalmente cuando vamos a construir una aplicación Web con Javascript tendremos que lidiar con una cantidad de ingente de herramientas como gestores de paquetes, transpiladores, linkers, builders, etc. El equipo de desarrollo de Facebook ha sabido ver esta problemática y se ha sacado de la manga el proyecto Create React App, el cual realizará por nosotros toda la configuración inicial necesaria para poder empezar a desarrollar con React.
Create React App se puede utilizar con el nuevo gestor de dependencias Yarn, creado también por la gente de Facebook, o con el clásico NPM. En el artículo haré uso de NPM, aunque te aconsejo que le des una oportunidad a Yarn, tiene muy buena pinta.
El único requisito imprescindible para poder hacer uso de Create React App con NPM es tener instalado en el sistema una versión de NodeJs >= 4. Si ya dispones de npm puedes instalar
create react app
como cualquier otro paquete:
npm install -g create-react-app
Una vez instalado puedes inicializar el proyecto:
create-react-app MyWebApp
Con este simple gesto tendrás configurado JavaScript ES6 con React, Webpack, Babel y Eslint, nada de instalar dependencias, ni de crear tareas. Está todo listo para ejecutar el servidor de desarrollo, y probar la aplicación:
cd MyWebApp npm start
Con el servidor corriendo, dirígete a la url 127.0.0.1:3000 para ver la aplicación en funcionamiento:
El proyecto generado tendrá una estructura tal que así:
MyWebApp/ README.md node_modules/ package.json public/ index.html favicon.ico src/ App.css App.js App.test.js index.css index.js logo.svg
Además encontrarás varios archivos sueltos, un readme, el .gitignore y el package.json, este último contiene las dependencias de npm además de la información del proyecto.
Es importante tener en cuenta que para que Create React App funcione correctamente tenemos que tener obligatoriamente el fichero principal de html en "public/index.html" y punto de entrada de javascript en "scr/index.js".
La app que se va a desarrollar constará de un formulario en el que el usuario podrá introducir un nombre y un email, y estos se añadirán a un listado (demo).
La web app estará formada por tres componentes que iremos construyendo a lo largo del artículo,
User
, UserList
, UserForm
; además del componente principal (App
) y el punto de entrada (index.js
. Puedes descargar el ejemplo completo desde mi cuenta de Github.
Como he comentado el punto de entrada a la aplicación es el fichero scr/index.js, en este se inicializa el componente principal App.js, a través del método
ReactDOM.Render
. Dicho método recibe como primer parámetro el componente a renderizar y como segundo el elemento del DOM donde el componente va ser renderizado:
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render( <App />, document.getElementById('root') );
A continuación vemos el código auto-generado que corresponde al componente principal:
import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; class App extends Component { render() { return ( <div className="App"> <div className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h2>Bienvenido a React</h2> </div> <p className="App-intro"> Lista de usuarios </p> </div> ); } } export default App;
Antes de continuar desarrollando el ejemplo voy eliminar unas cuantas líneas de este componente para dejarlo, por ahora, lo más simple posible:
import React, { Component } from 'react'; class App extends Component { render() { return ( <h1>Hello World!</h1> ); } } export default App;
En la primera línea
import React…
se está importando la librería React y la clase Component de la cual van a heredar todos los componentes que se creen mediante clases. Éstas requieren del método render()
para poder funcionar.
En la versión previa de Javascript se utilizaba la función
React.createClass
para inicializar componentes, gracias a ES6 y a su azúcar sintáctico, esto se ha simplificado.
Las propiedades de un componente (props) pueden definirse como los atributos de configuración para dicho componente. Éstas son recibidas desde un nivel superior, normalmente al realizar la instanciación del componente y por definición son inmutables.
Siguiendo con el ejemplo, voy a implementar el componente
User
, el cual contiene dos props name
y user
, las cuales redenrizará en un elemento de lista li
.
import React, { Component } from 'react'; class User extends Component { render () { return ( <li> {this.props.name} - {this.props.email} </li> ); } } export default User;
Las props, basicamente, son el mecanismo principal de React para pasar datos de un componente padre a un componente hijo.
Una vez creado el componente User, definiremos el componente
UserList
, cuyo objetivo será renderizar una lista de componentes User
:
class UserList extends Component { render () { return ( <ul> {this.props.users.map(u => { return ( <User key={u.id} name={u.name} email={u.email} /> ); })} </ul> ); } }
En el código del componente anterior renderizará una lista de usuarios, para ello hace uso del método
map
, con el cual itera sobre cada uno de los elementos del array de usuarios que contiene la propiedad this.props.users
, esta prop será recibida desde el componente App
.
Map devuelve por cada elemento un componente
User
, el cual recibe vía props el nombre, el email y una key. La propiedad key es un identificador que usa React en las listas, para renderizar los componentes de forma más eficiente.
Podría definirse el estado de un componente como una representación del mismo en un momento concreto, algo así como una instantánea del componente. Dicho estado se iniciará con un valor por defecto.
Existen dos tipos de componentes con y sin estado, también denominados statefull y stateless, respectivamente.
Todos los componentes implementados hasta el momento han sido stateless, sin estado. Este tipo de componentes podrían representarse como una función pura:
function User(props) { return ( <li>{props.name} - {props.email}</li> ); }
Las funciones puras por definición no tienen efectos colaterales, con lo cual este tipo de componentes no admite ciclos de vida. Para no complicar las cosas, continuaremos creando nuestros componentes como clases.
Los componentes con estado permiten mantener datos propios a lo largo del tiempo e implementar comportamientos en sus diferentes métodos del ciclo de vida.
Antes de continuar merece la pena repasar los estados del ciclo de vida de un componente:
Se produce cuando una instancia del componente está siendo creada e insertada en el DOM.
constructor()
, aquí inicializamos el estado del componente y hacemos el setup inicial.componentWillMount()
, se ejecuta antes de realizar el montaje, antes de llevar a cabo la operación de render.render()
, método en el que se renderiza el componente y sus componentes hijos.componentDidMount()
, este método se ejecuta después de que se haya renderizado el componente.Se produce cuando las props o el estado cambia y el componente se vuelve a renderizar.
componentWillReceiveProps()
, este método se invoca cuando el componente está a punto de recibir nuevas props.shouldComponentUpdate()
, este método se invoca antes rederizar, para preguntar al componente si quiere volver a renderizarse, devuelve un booleanocomponentWillUpdate()
, se invoca justo antes de renderizar, para preparar el próximo renderrender()
componentDidUpdate()
, se invoca después del renderSe produce cuando un componente va a dejar de renderizarse.
componentWillUnmount()
, este método se ejecuta justo antes de que un componente sea desmontado.Continuando con el ejemplo, vamos a modificar el componente principal, convirtiendolo en un componente con estado en el que mantendríamos los usuarios de la aplicación. Para ello usamos el constructor que recibe vía parámetro props y le asigna el estado inicial:
import React, { Component } from 'react'; import User from './components/user' import UserList from './components/userList' class App extends Component { constructor(props) { super(props); this.state = { users: [ {id: 1, name: 'Miguel', email: 'miguel@email.com'}, {id: 2, name: 'Jorge', email: 'jorge@email.com'} ] }; } render() { return ( <div> <h1>Lista de usuarios</h1> <UserList users={this.state.users}/> </div> ); } } export default App;
En este caso, en el
constructor
asignamos un array con 2 usuarios al estado inical y en el método render
le pasamos los usuarios que se encuentran en el this.state
al componente UserList
que hemos creado anteriormente.
La aplicación no estaría completa sin permitir a los usuarios añadir elementos a la lista. Para ello requerimos de un formulario y de un método para añadir elementos a nuestro array de usuarios.
El estado en React es inmutable, pero, aunque no podamos mutarlo, si que disponemos de un método para poder reasignarlo. Para actualizar el estado de un componente en react, debemos usar el método
setState()
, el cual acepta un objeto plano que se fusionará con el estado previo del componente:
this.setState({ users: users });
El componente
UserForm
será un componente con estado cuyo objetivo es mantener un seguimiento de los valores de los campos del formulario:
class UserForm extends Component { constructor (props) { super(props); this.state = { name: '', email: '' }; this.handleOnChange = this.handleOnChange.bind(this); this.handleOnSubmit = this.handleOnSubmit.bind(this); } handleOnChange (event) { // Cada vez que cambian los campos del formualario, // actualizamos el estado const target = event.target; const value = target.value; const name = target.name; this.setState({ [name]: value }); } handleOnSubmit (event) { event.preventDefault(); // Cogemos la función onAdd que nos llega vía props y le pasamos el state this.props.onAdd(this.state); // Reiniciamos el estado this.setState({ name: '', email: '' }); } render () { return ( <form onSubmit={this.handleOnSubmit}> <div> <label>Nombre: <input name="name" type="text" value={this.state.name} onChange={this.handleOnChange} /> </label> </div> <div> <label>Email: <input name="email" type="mail" value={this.state.email} onChange={this.handleOnChange} /> </label> </div> <input type="submit" value="Add" /> </form> ); } }
En este componente capturamos cada vez que el usuario cambia un input mediante la propiedad
onChange
del elemento. Dicha propiedad recibe una función handleOnChange
, que usa el método setState
para actualizar el estado del componente. De igual forma, capturamos cuando el usuario hace click en el botón de enviar mediante la propiedad onSubmit
, la cual dispara al método handleOnSubmit
.
La función
handleOnSubmit
llama a la función onAdd
,que se reciben a través de la propiedad props
del componente UserForm
, con los datos del estado actual del formulario. Esta propiedad la vamos a definir en el componente App
, para conectar ambos componentes:
import React, { Component } from 'react'; import User from './components/user' import UserList from './components/userList' import UserForm from './components/userForm' class App extends Component { constructor(props) { super(props); this.handleOnAdd = this.handleOnAdd.bind(this); this.state = { users: [ {id: 1, name: 'Miguel', email: 'miguel@email.com'}, {id: 2, name: 'Jorge', email: 'jorge@email.com'} ], nextId: 3 }; } handleOnAdd(user) { // Copiamos el array const users = [...this.state.users]; const nextId = this.state.nextId; users.push({...user, id: nextId}); // Actualizamos el estado this.setState({ users: users, nextId: nextId + 1 }); } render() { return ( <div> <h1>Lista de usuarios</h1> <UserList users={this.state.users}/> <UserForm onAdd={this.handleOnAdd}/> </div> ); } } export default App;
Hemos añadido al estado un nextId para asociarlo a los usuarios que vamos creando, añadido el método
handleOnAdd()
, el cual modifica el array de usuarios y reasigna el estado para reflejar los cambios. Por último, se incluye el componente UserForm
en el método render()
y se le pasa la función recién creada cómo parámetro, mediante la propiedad onAdd
.
Con esto ya se tendría un función similar a la que se puede ver en la demo.
Hemos hecho un repaso de los conceptos básicos de React como son la anidación de componentes, el paso de información mediante props y la actualización mediante el uso del estado; todo ello implementando un ejemplo simple de lista de usuarios, que responde a las bases de lo que es Single Page Application (SPA). Vale la pena destacar que el ejemplo seguramente no es la mejor forma de desarrollar una aplicación robusta, ya que no hemos hecho uso de Redux ni de contenedores. Sin embargo, creo que puede ser una excelente forma de entender el flujo de los datos en una aplicación React.
A continuación dejo algunos enlaces que te pueden ser de ayuda:
En futuros artículos veremos cómo usar React para construir aplicaciones más completas, haciendo uso de Redux y Sagas. ¡Saludos!