Hamburger Icon
React para principiantes

React para principiantes

Toda interfaz de usuario se compone de pequeños elementos como botones, enlaces, textos, etc. React permite combinar, crear y anidar en un árbol lógico todos estos elementos a los que llamaremos componentes.

Según la documentación oficial, React es una librería para renderizar la interfaz de usuario o parte de ella. Es muy habitual que nos refiramos a React como un framework, pero realmente no lo es.

React es una librería porque tiene únicamente una función: renderizar la interfaz de usuario. Otros frameworks como AngularJS o Vue incluyen otras muchas funcionalidades por defecto, como la gestión de estado o enrutamiento de la aplicación.

Esto no quiere decir que si usas React no puedas tener lo mismo que con otros frameworks, simplemente que esas funcionalidades van por separado: usaremos las librerías Redux para la gestión de estados o react-router para el enrutamiento. Estas son las opciones habituales, pero hay otras y puedes escoger en todo momento.

Todo esto permite que React sea muy fácil de implantar en un sistema existente de forma gradual, y seguramente es uno de los motivos por los que es la opción más usada actualmente en el desarrollo de UI.

Instalar React

Añadir react a una web existente

Si no quieres instalar nada y simplemente quieres añadir la librería React en una web existente, tendrás que:

  • Crear un nuevo contenerdor que contendrá el resultado de la renderización que hará React
  • Añadir un par de etiquetas script con la llamada a la librería React

El contenedor lo podrás poner donde quieras, pero es importante que le añadas un identificador que usarás después para decirle a React dónde tiene que meter su resultado, algo como esto:

<!-- ... otro código HTML ... -->

<div id="mi-componente"></div>

<!-- ... otro código HTML ... -->

Las librerías que necesitarás añadir en un primer momento son estas:

    <!-- ... otro código HTML ... -->

    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
    <script src="mi-componente.js"></script>
  </body>
</html>

Una librería permite definir los componentes y la otra renderizarlos en el DOM. Finalmente, necesitarás crear el archivo mi-componente.js:

// mi-componente.js
"use strict"

// el componente
const MiComponente = () => {
  return React.createElement("h1", {}, "Hola Mundo React")
}

// renderizado
const rootNode = document.getElementById("mi-componente")
const root = ReactDOM.createRoot(rootNode)
root.render(React.createElement(MiComponente))

Y a partir de aquí, puedes crear y reusar todos los componentes que necesites.

Muy importante: cuando termines el desarrollo, tendrás que modificar las etiquetas script actuales y sustituir development.js por production.min.js.

Crear un proyecto React

Cuando empieces un proyecto con React, entonces sí que usarás el framework completo: React más todas las librerías y herramientas habituales. Para ello, abriremos nuestra consola de comandos (debemos tener Node instalado) y ejecutaremos este comando:

npx create-react-app nombre-proyecto-react

Para ejecutar la aplicación, entraremos en el directorio del proyecto y ejecutaremos este comando:

npm start

¿Fácil verdad? El comando create-react-app ya instala toda una serie de librerías y herramientas que nos permitirán hacer todo lo que necesitamos en cualquier aplicación React, aunque también podremos instalar otros paquetes npm según necesitemos, claro.

La estructura por defecto del proyecto será como esta:

nombre-proyecto-react
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── serviceWorker.js
    └── setupTests.js

Muy recomendable revisar la documentación oficial de React para empezar entender los conceptos básicos. En el momento de escribir este post, la documentación todavía usa clases para definir componentes, pero se puede consultar la documentación en fase beta con orientación a funciones en https://beta.reactjs.org/

Qué es JSX

En el primer ejemplo que hemos visto de React, donde añadíamos la librería a una web existente, hemos usado la función React.CreateElement(). Esto no es lo más habitual, normalmente usaremos algo llamado JSX que es mucho más legible.

El código JSX es muy parecido a HTML, pero al que le podemos poner expresiones JavaScript. Al desarrollar aplicaciones interactivas, es muy interesante poder usar algo como JSX para juntar la parte HTML con JavaScript. Puede parecer algo antinatural, pero en poco tiempo de uso se siente como algo de lo más normal. Se le acaba pillando gustillo.

El código JSX lo escribiremos en el return del componente, y necesitaremos tener en cuenta algunas reglas especiales.

Devuelve solo un elemento raíz

El return del componente solo puede tener un elemento raíz. Esto está mal:

// ¡mal!
return (
  <h1>Hola Mundo React</h1>
  <h2>Una introducción a React</h2>
)

Lo correcto es englobar todo en un solo elemento raíz, un div por ejemplo:

// ¡bien!
return (
  <div>
    <h1>Hola Mundo React</h1>
    <h2>Una introducción a React</h2>
  </div>
)

¿Y si no quieres un elemento <div> extra en tu código? Pues no pasa nada, puedes hacer esto:

// ¡bien!
return (
  <>
    <h1>Hola Mundo React</h1>
    <h2>Una introducción a React</h2>
  </>
)

El tag <> se llama fragment y te permite agrupar elementos sin dejar ningún rastro en el HTML resultante.

Cierra todas las etiquetas HTML

No dejes ningún tag HTML sin cerrar. En HTML podemos usar a veces tags y no cerrarlos, como en los listados o las imágenes, porque el navegador lo interpreta y lo entiende. Aquí toca ser más correctos y precisos.

// ¡mal!
return <img src="..." alt="...">
// ¡bien!
return <img src="..." alt="..." />

Uso de formato camelCase en casi todos los atributos

JSX covierte el contenido que picamos en objetos JavaScript. Cuando manipulemos un componente podremos acceder a mucha información mediante variables. Como JavaScript tiene algunas limitaciones en cuanto al nombre de las variables, deberemos adaptarnos un poco a eso.

Todos los atributos HTML que se escriban con guiones, como stroke-width, los pondremos de esta forma: strokeWidth. Además, como class es una palabra reservada en JavaScript, pondremos las clases CSS en el atributo className. Al principio puede ser que olvides esto, pero no pasa nada, en caso de error React mostrará en la consola dónde está el problema.

Empezamos a programar

Vamos a desarrollar una aplicación muy simple. Realmente, no haremos una aplicación entera, simplemente crearemos una serie de componentes para crear un carrito de la compra. Este carrito deberá indicar cuántos elementos contiene, además de actualizar este valor si se añaden o se quitan elementos del mismo.

El código final de esta aplicación lo puedes encontrar en el repositorio Zeppelin17/react-tutorial-basico.

Para empezar, tenemos este código:

import { useState } from "react"

const App = () => {
  // estado
  const [numProductos, setNumProductos] = useState(0)

  // functiones para manipular estado
  const addElemento = () => {
    setNumProductos(numProductos + 1)
  }

  const removeElemento = () => {
    if (numProductos > 0) {
      setNumProductos(numProductos - 1)
    }
  }

  // renderizado del componente
  return (
    <div>
      <div>Carrito: {numProductos}</div>
      <div>
        <button onClick={addElemento}>Añadir artículo</button>
        <button onClick={removeElemento}>Eliminar artículo</button>
      </div>
    </div>
  )
}

export default App

Aquí, de golpe y porrazo, se presentan muchas cosas. Estamos usando un hook, estamos definiendo funciones de lógica de la aplicación y estamos usando JSX para renderizar el carrito de la compra.

Hook useState

import { useState } from "react"

const [numProductos, setNumProductos] = useState(0)

Lo primero es ver que estamos importando el hook useState, que nos permite almacenar estado en el componente. Este hook funciona del siguiente modo: definimos el nombre de la variable que almacenará un cierto valor, numProductos y por otro definimos la función que permitirá actualizar dicho valor, setNumProducts. El valor inicial lo indicamos directamente al llamar a la función useState, que devolverá tanto este valor inicial como la lógica de la función para actualizar el estado.

Esto es lo bueno de los hooks, la lógica queda encapsulada y no tenemos que preocuparnos de ella, sabemos que setNumProductos va a funcionar y va a actualizar numProductos cuando le pasemos un nuevo valor.

Puedes ver más detalles del hook useState en la documentación oficial.

Funciones del componente

El componente principal va a mostrar el total de productos que tenemos en el carrito, por lo que necesitaremos definir un par de funciones que se encarguen de esta lógica. Esto lo hacemos con addElemento y removeElemento.

const addElemento = () => {
  setNumProductos(numProductos + 1)
}

const removeElemento = () => {
  if (numProductos > 0) {
    setNumProductos(numProductos - 1)
  }
}

En estas funciones simplemente usamos el método de actualización de estado setNumProductos que hemos definido antes para actualizar el estado, teniendo en cuenta que nunca podemos tener un número negativo de elementos.

Renderizado del componente

La función principal devuelve código JSX. Ahí indicamos el número actual de elementos que tiene el carrito y ponemos dos botones a disposición del usuario para que manipule el estado. Un botón para incrementar el número de elementos y otro para disminuirlo.

return (
  <div>
    <div>Carrito: {numProductos}</div>
    <div>
      <button onClick={addElemento}>Añadir artículo</button>
      <button onClick={removeElemento}>Eliminar artículo</button>
    </div>
  </div>
)

Estos botones, en su evento onClick, llaman a las funciones que hemos definido antes para manipular el estado: addElemento y removeElemento.

El resultado de esto es muy simple, pero funciona. Tenemos un componente de carrito que permite indicar el total de artículos que hay en él, añadir elementos y quitarlos.

Refactorización

Vale, ahora que funciona y entendemos lo que está pasando, toca poner todo en su lugar. Vamos a dividir nuestro código en varios componentes. La mayor ventaja de dividir el código en componentes es que podremos reutilizarlos más adelante sin necesidad de repetir código, por lo tanto, mantenerlo también será mucho más trivial.

Por un lado, crearemos un componente <CustomButton> que nos permita añadir un botón. También crearemos un componente <Cart> que será el carrito en sí. Finalmente, estructuraremos toda la lógica en diferentes archivos.

Componente botón

Este es el resultado, que comentaremos a continuación:

import { useState } from "react"

// componente de botón personalizado
const CustomButton = ({ children, handleClick }) => {
  return <button onClick={handleClick}>{children}</button>
}

// componente principal
const App = () => {
  const [numProductos, setNumProductos] = useState(0)

  const addElemento = () => {
    setNumProductos(numProductos + 1)
  }

  const removeElemento = () => {
    if (numProductos > 0) {
      setNumProductos(numProductos - 1)
    }
  }

  return (
    <div>
      <div>Carrito: {numProductos}</div>
      <div>
        <CustomButton handleClick={addElemento}>Añadir artículo</CustomButton>
        <CustomButton handleClick={removeElemento}>
          Eliminar artículo
        </CustomButton>
      </div>
    </div>
  )
}

export default App

Vemos que en la zona superior hemos definido una nueva función que será nuestro componente botón:

const CustomButton = ({ children, handleClick }) => {
  return <button onClick={handleClick}>{children}</button>
}

En estas tres líneas de código están pasando muchas cosas. En primer lugar, estamos pasando parámetros al componente. Todos los parámetros que se le pasan están almacenados en la variable props, que destructuramos mediante { prop1, prop2, ...} para acceder únicamente a los parámetros que nos interesen. Esto sería un código equivalente:

const CustomButton = (props) => {
  return <button onClick={props.handleClick}>{props.children}</button>
}

Quédate con la versión que quieras, yo personalmente soy partidario de destructurar los parámetros para una mejor legibilidad.

Sigamos entonces. En este componente de botón vamos a pasar dos parámetros:

  • La función que se llamará al hacer clic en el propio botón, la nombraremos handleClick por convención
  • los elementos hijos del botón, es decir, todos los nodos que contengan sus etiquetas de abertura y cierre

Los hijos de un componente siempre irán en la variable children, esto no cambia en ningún componente.

Para usar este componente que acabamos de crear, solo tendremos que usar el correspondiente tag HTML, determinado por el nombre de la función que contiene su lógica y lo renderiza:

<CustomButton handleClick={addElemento}>Añadir artículo</CustomButton>

Como podemos observar, al componente <CustomButton> le pasamos en el parámetro handleClick la función que le corresponda. Además, como hemos comentado, todo el código HTML que pongamos entre sus dos etiquetas se pasará en el parámetro children.

Este botón lo podemos usar todas las veces que queramos, todos serán independientes entre ellos.

Componente carrito

Este cambio va a ser un poco más sutil, pero muy útil igualmente. Mira:

import { useState } from "react"

// componente de botón personalizado
const CustomButton = ({ children, handleClick }) => {
  return <button onClick={handleClick}>{children}</button>
}

// componente carrito
const Cart = () => {
  const [numProductos, setNumProductos] = useState(0)

  const addElemento = () => {
    setNumProductos(numProductos + 1)
  }

  const removeElemento = () => {
    if (numProductos > 0) {
      setNumProductos(numProductos - 1)
    }
  }

  return (
    <div>
      <div>Carrito: {numProductos}</div>
      <div>
        <CustomButton handleClick={addElemento}>Añadir artículo</CustomButton>
        <CustomButton handleClick={removeElemento}>
          Eliminar artículo
        </CustomButton>
      </div>
    </div>
  )
}

// componente principal
const App = () => {
  return <Cart />
}

export default App

Hemos encapsulado toda la lógica del carrito en su propio componente. Ahora, desde el componente principal solamente devolvemos el componente <Cart>:

const App = () => {
  return <Cart />
}

El componente carrito queda encapsulado de esta forma:

const Cart = () => {
  const [numProductos, setNumProductos] = useState(0)

  const addElemento = () => {
    setNumProductos(numProductos + 1)
  }

  const removeElemento = () => {
    if (numProductos > 0) {
      setNumProductos(numProductos - 1)
    }
  }

  return (
    <div>
      <div>Carrito: {numProductos}</div>
      <div>
        <CustomButton handleClick={addElemento}>Añadir artículo</CustomButton>
        <CustomButton handleClick={removeElemento}>
          Eliminar artículo
        </CustomButton>
      </div>
    </div>
  )
}

Aunque parezca a simple vista un cambio poco notorio, esta encapsulación permite reutilizar el componente y mantenerlo aislado de los demás. Si hacemos esto:

const App = () => {
  return (
    <>
      <Cart />
      <Cart />
      <Cart />
    </>
  )
}

Tendremos tres carritos y cada uno de ellos gestionará su propio estado y contexto, sin entrar en conflicto el uno con el otro. Aunque en este caso concreto no tenga sentido alguno hacer algo así, esto nos da una flexibilidad enorme al crear nuestros componentes.

Organizando cada componente en su archivo

Es bastante habitual que cada componente esté en su propio archivo, por lo tanto, una posible organización sería la siguiente. En el archivo principal de la aplicación:

// src/App.js
import Cart from "./components/Cart"

const App = () => {
  return <Cart />
}

export default App

En el archivo Cart.js en la carpeta components:

// src/components/Cart.js
import { useState } from "react"
import CustomButton from "./CustomButton"

const Cart = () => {
  const [numProductos, setNumProductos] = useState(0)

  const addElemento = () => {
    setNumProductos(numProductos + 1)
  }

  const removeElemento = () => {
    if (numProductos > 0) {
      setNumProductos(numProductos - 1)
    }
  }

  return (
    <div>
      <div>Carrito: {numProductos}</div>
      <div>
        <CustomButton handleClick={addElemento}>Añadir artículo</CustomButton>
        <CustomButton handleClick={removeElemento}>
          Eliminar artículo
        </CustomButton>
      </div>
    </div>
  )
}

export default Cart

Y finalmente el archivo CustomButton.js en la carpeta components:

// src/components/_CustomButton.js
const CustomButton = ({ children, handleClick }) => {
  return <button onClick={handleClick}>{children}</button>
}

export default CustomButton

Con esta separación cada componente es independiente y podemos reutilizarlos todos según nos convenga.

Conclusiones

React es fabuloso. Igual que Vue, Svelte, Angular, Eleventy, Astro... Estas abstracciones por encima de JavaScript nos permiten desarrollar de forma rápida añadiendo mucha complejidad que queda escondida y de la que no nos tenemos que preocupar, aunque sí es importante entenderla para no verse encallado con errores más adelante.

La idea del componente vino para quedarse. Esta orientación nos permite reutilizar código no ya solo dentro del proyecto, sino entre proyectos diferentes. Verás por internet multitud de librerías de componentes que te dan hechas galerías de imágenes, botones, enlaces, estructuras de texto en varias columnas, menús y una infinidad más de elementos.

¿Te animas a crear tu próxima aplicación con React?