Saltar al contenido

a

Introducción a React

Ahora comenzaremos a familiarizarnos con probablemente el tema más importante de este curso, es decir, la librería React. Comencemos con la creación de una aplicación React simple y con el conocimiento de los conceptos básicos de React.

La forma más fácil de empezar es utilizando una herramienta llamada Vite.

Comencemos creando una aplicación llamada part1, naveguemos a su directorio e instalemos las librerías:

# npm 6.x (desactualizado, pero aun en uso por algunos):
npm create vite@latest part1 --template react

# npm 7+, el doble guion adicional es necesario:
npm create vite@latest part1 -- --template react
cd part1
npm install

La aplicación se inicia de la siguiente manera

npm run dev

La consola indica que la aplicación ha iniciado en localhost, puerto 5173, es decir la dirección http://localhost:5173/:

Captura de pantalla de la consola ejecutando vite en localhost 5173

Vite inicia la aplicación por defecto en el puerto 5173. Si este no está libre, Vite utiliza el siguiente numero de puerto libre.

Abre el navegador y un editor de código para que puedas ver el código y el navegador al mismo tiempo en la pantalla:

Captura de pantalla de la pagina inicial de vite y estructura de archivos en vs code

El código de la aplicación se encuentra en la carpeta src. Simplifiquemos el código predeterminado de tal modo que el archivo main.jsx se vea así:

import ReactDOM from 'react-dom/client'

import App from './App'

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

y el archivo App.jsx se vea así:

const App = () => {
  return (
    <div>
      <p>Hello world</p>
    </div>
  )
}

export default App

Los archivos App.css y index.css, y el directorio assets pueden eliminarse ya que nos son necesarios en nuestra aplicación por ahora.

create-react-app

En lugar de Vite, tu puedes usar la vieja herramienta de generación create-react-app en el curso para inicializar aplicaciones. La diferencia más visible es el nombre del archivo de arranque de la aplicación, el cual es index.js.

La manera de iniciar la aplicación también es diferente en CRA, en esta se inicia con el comando

npm start

en contraste con Vite

npm run dev

Componente

El archivo App.js ahora define un componente de React con el nombre App. El comando en la línea final del archivo main.jsx

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

renderiza su contenido dentro del elemento div, definido en el archivo index.html, que tiene el valor 'root' en el atributo id.

De forma predeterminada, el archivo index.html no contiene ningún marcado HTML que sea visible para nosotros en el navegador:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

Puedes intentar agregar algo de HTML al archivo. Sin embargo, cuando se usa React, todo el contenido que necesita ser renderizado es generalmente definido como componentes de React.

Echemos un vistazo mas de cerca al código que define el componente:

const App = () => (
  <div>
    <p>Hello world</p>
  </div>
)

Como probablemente adivinaste, el componente se renderiza como una etiqueta div, que envuelve una etiqueta p que contiene el texto Hello world.

Técnicamente, el componente se define como una función de JavaScript. La siguiente es una función (que no recibe ningún parámetro):

() => (
  <div>
    <p>Hello world</p>
  </div>
)

La función luego se asigna a una variable constante App:

const App = ...

Hay algunas formas de definir funciones en JavaScript. Aquí utilizaremos funciones de flecha, que se describen en una versión más reciente de JavaScript conocida como ECMAScript 6, también llamada ES6.

Debido a que la función consta de una sola expresión, hemos utilizado una abreviatura, que representa este fragmento de código:

const App = () => {
  return (
    <div>
      <p>Hello world</p>
    </div>
  )
}

En otras palabras, la función devuelve el valor de la expresión.

La función que define el componente puede contener cualquier tipo de código JavaScript. Modifica tu componente de la siguiente manera:

const App = () => {
  console.log('Hello from component')
  return (
    <div>
      <p>Hello world</p>
    </div>
  )
}

export default App

y observa lo que sucede en la consola:

Consola del navegador mostrando un registro en consola con flecha a "Hello from component"

La primera regla del desarrollo web frontend:

deja la consola abierta todo el tiempo

Repitamos esto juntos: Prometo dejar la consola abierta todo el tiempo durante este curso, y por el resto de mi vida mientras esté haciendo desarrollo web.

También es posible renderizar contenido dinámico dentro de un componente.

Modifica el componente de la siguiente manera:

const App = () => {
  const now = new Date()
  const a = 10
  const b = 20
  console.log(now, a+b)

  return (
    <div>
      <p>Hello world, it is {now.toString()}</p>
      <p>
        {a} plus {b} is {a + b}
      </p>
    </div>
  )
}

Cualquier código de JavaScript entre llaves es evaluado y el resultado de esta evaluación se incrusta en el lugar definido en el HTML producido por el componente.

Recuerda que no deberías eliminar la línea al final del componente

export default App

El export no se muestra en la mayoría de los ejemplos del material de este curso. Sin este export el componente y la aplicación completa se romperían.

¿Recuerdas que prometiste dejar la consola abierta? ¿Qué se imprimió allí?

JSX

Parece que los componentes de React están devolviendo marcado HTML. Sin embargo, éste no es el caso. El diseño de los componentes de React se escribe principalmente usando JSX. Aunque JSX se parece a HTML, en realidad estamos tratando con una forma de escribir JavaScript. Bajo el capó, el JSX devuelto por los componentes de React se compila en JavaScript.

Después de compilar, nuestra aplicación se ve así:

const App = () => {
  const now = new Date()
  const a = 10
  const b = 20
  return React.createElement(
    'div',
    null,
    React.createElement(
      'p', null, 'Hello world, it is ', now.toString()
    ),
    React.createElement(
      'p', null, a, ' plus ', b, ' is ', a + b
    )
  )
}

La compilación está a cargo de Babel. Los proyectos creados con create-react-app o vite están configurados para compilarse automáticamente. Aprenderemos más sobre este tema en la parte 7 de este curso.

También es posible escribir React como "JavaScript puro" sin usar JSX. Aunque, nadie que este cuerdo lo haría.

En la práctica, JSX se parece mucho a HTML con la distinción de que con JSX puede incrustar fácilmente contenido dinámico escribiendo JavaScript entre llaves. La idea de JSX es bastante similar a muchos lenguajes de plantillas, como Thymeleaf, utilizado junto con Java Spring, que se utiliza en servidores.

JSX es similar a XML, lo que significa que todas las etiquetas deben cerrarse. Por ejemplo, una nueva línea es un elemento vacío, que en HTML se puede escribir de la siguiente manera:

<br>

pero al escribir JSX, la etiqueta debe estar cerrada:

<br />

Componentes múltiples

Modifiquemos el archivo App.jsx de la siguiente manera:

const Hello = () => {  return (    <div>      <p>Hello world</p>    </div>  )}
const App = () => {
  return (
    <div>
      <h1>Greetings</h1>
      <Hello />    </div>
  )
}

Hemos definido un nuevo componente Hello y lo usamos dentro del componente App. Naturalmente, un componente se puede usar múltiples veces:

const App = () => {
  return (
    <div>
      <h1>Greetings</h1>
      <Hello />
      <Hello />      <Hello />    </div>
  )
}

Nota: El export al final se omite en estos ejemplos, ahora y en el futuro. Todavía será necesario para que el código funcione.

Escribir componentes con React es fácil, y al combinar componentes, incluso una aplicación más compleja puede ser bastante fácil de mantener. De hecho, una filosofía central de React es componer aplicaciones a partir de muchos componentes reutilizables especializados.

Otra fuerte convención es la idea de un componente raíz llamado App en la parte superior del árbol de componentes de la aplicación. Sin embargo, como aprenderemos en la parte 6, hay situaciones en las que el componente App no es exactamente la raíz, sino que está incluido en un componente de utilidad apropiado.

props: pasar datos a componentes

Es posible pasar datos a componentes usando los llamados props.

Modifiquemos el componente Hello de la siguiente manera:

const Hello = (props) => {  return (
    <div>
      <p>Hello {props.name}</p>    </div>
  )
}

Ahora la función que define el componente tiene un parámetro props. Como argumento, el parámetro recibe un objeto, que tiene campos correspondientes a todos los "props" ("accesorios") que el usuario del componente define.

Los props se definen de la siguiente manera:

const App = () => {
  return (
    <div>
      <h1>Greetings</h1>
      <Hello name='George' />      <Hello name='Daisy' />    </div>
  )
}

Puede haber un número arbitrario de props y sus valores pueden ser strings "incrustados en el código" ("hard coded") o resultados de expresiones JavaScript. Si el valor del prop se obtiene usando JavaScript, debe estar envuelto con llaves.

Modifiquemos el código para que el componente Hello use dos props:

const Hello = (props) => {
  console.log(props)  return (
    <div>
      <p>
        Hello {props.name}, you are {props.age} years old      </p>
    </div>
  )
}

const App = () => {
  const name = 'Peter'  const age = 10
  return (
    <div>
      <h1>Greetings</h1>
      <Hello name='Maya' age={26 + 10} />      <Hello name={name} age={age} />    </div>
  )
}

Los props enviados por el componente App son los valores de las variables, el resultado de la evaluación de la expresión de suma y un string regular.

El componente Hello también imprime en consola el valor del objeto props.

Yo realmente espero que tu consola esté abierta. Si no es asi, recuerda tu promesa:

Prometo dejar la consola abierta todo el tiempo durante este curso, y por el resto de mi vida mientras esté haciendo desarrollo web.

El desarrollo de software es difícil. Este se vuelve aun más difícil si uno no está usando todas las herramientas disponibles como la consola de desarrollo e imprimiendo la depuración con console.log. Los profesionales usan ambas todo el tiempo y no hay una sola razón de porque un principiante no deberías adoptar estos maravillosos métodos de ayuda que le harán la vida mucho más fácil.

Posible mensaje de error

Dependiendo del editor que estés usando, podrías recibir un mensaje de error en este punto:

Captura de pantalla de vs code mostrando un error de eslint: "name is missing in props validation"

Este realmente no es un error, es una advertencia causada por la herramienta ESLint. Puedes silenciar la advertencia react/prop-types añadiendo la siguiente línea al archivo .eslintrc.cjs

module.exports = {
   root: true,
   env: { browser: true, es2020: true },
   extends: [
     'eslint:recommended',
     'plugin:react/recommended',
     'plugin:react/jsx-runtime',
     'plugin:react-hooks/recommended',
   ],
   ignorePatterns: ['dist', '.eslintrc.cjs'],
   parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
   settings: { react: { version: '18.2' } },
   plugins: ['react-refresh'],
   rules: {
     'react-refresh/only-export-components': [
       'warn',
       { allowConstantExport: true },
     ],
     'react/prop-types': 0   },
}

Aprenderemos sobre ESLint más en detalle en la parte 3.

Algunas notas

React se ha configurado para generar mensajes de error bastante claros. A pesar de esto, debes, al menos al principio, avanzar en pasos muy pequeños y asegurarte de que cada cambio funcione como deseas.

La consola siempre debe estar abierta. Si el navegador reporta errores, no es recomendable seguir escribiendo más código, esperando milagros. En su lugar, debes intentar comprender la causa del error y, por ejemplo, volver al estado funcional anterior:

Captura de pantalla de error de prop: undefined

Es bueno recordar que en React es posible y vale la pena escribir comandos console.log() (que se imprimen en la consola) dentro de tu código.

También ten en cuenta que los nombres de los componentes de React deben comenzar con mayúscula. Si intentas definir un componente de la siguiente manera:

const footer = () => {
  return (
    <div>
      greeting app created by <a href='https://github.com/mluukkai'>mluukkai</a>
    </div>
  )
}

y lo usas de esta manera:

const App = () => {
  return (
    <div>
      <h1>Greetings</h1>
      <Hello name='Maya' age={26 + 10} />
      <footer />    </div>
  )
}

la página no mostrará el contenido definido dentro del componente footer, y en su lugar React solo crea un elemento footer vacío. Si cambias la primera letra del nombre del componente a una letra mayúscula, React crea el elemento div definido en el componente Footer, que se renderiza en la página.

Ten en cuenta que el contenido de un componente de React (normalmente) debe contener un elemento raíz. Si, por ejemplo, intentamos definir el componente App sin el elemento div más externo:

const App = () => {
  return (
    <h1>Greetings</h1>
    <Hello name='Maya' age={26 + 10} />
    <Footer />
  )
}

el resultado es un mensaje de error.

Captura de pantalla del error multiples elementos de raíz

Usar un elemento raíz no es la única opción que funciona. Un array de componentes también es una solución válida:

const App = () => {
  return [
    <h1>Greetings</h1>,
    <Hello name='Maya' age={26 + 10} />,
    <Footer />
  ]
}

Sin embargo cuando se define el componente raíz de la aplicación, hacer esto no es algo particularmente sabio, y hace que el código se vea un poco desagradable.

Debido a que el elemento raíz está estipulado, tenemos elementos div "extra" en el árbol DOM. Esto se puede evitar usando fragments, es decir, envolviendo los elementos que el componente devolverá con un elemento vacío:

const App = () => {
  const name = 'Peter'
  const age = 10

  return (
    <>
      <h1>Greetings</h1>
      <Hello name='Maya' age={26 + 10} />
      <Hello name={name} age={age} />
      <Footer />
    </>
  )
}

Ahora este se compila con éxito y el DOM generado por React ya no contiene el elemento div adicional.

No renderizar objetos

Considera una aplicación que imprime en pantalla los nombres y edades de nuestros amigos:

const App = () => {
  const friends = [
    { name: 'Peter', age: 4 },
    { name: 'Maya', age: 10 },
  ]

  return (
    <div>
      <p>{friends[0]}</p>
      <p>{friends[1]}</p>
    </div>
  )
}

export default App

Sin embargo, nada aparece en la pantalla. He tratado de buscar el problema en el código por 15 minutos, pero no he podido encontrar cual puede ser el problema.

Finalmente recordé la promesa que hice

Prometo dejar la consola abierta todo el tiempo durante este curso, y por el resto de mi vida mientras esté haciendo desarrollo web.

La consola grita en rojo:

Consola mostrando error resaltado acerca de "Objects are not valid as a React child"

La raíz del problema es Objects are not valid as a React child (Los objetos no son válidos como elementos hijos de React), es decir, la aplicación intentó renderizar objetos y falló nuevamente.

El código trató de renderizar la información de un amigo de la siguiente manera:

<p>{friends[0]}</p>

y esto causó un problema porque el item a ser renderizado en las llaves es un objeto.

{ name: 'Peter', age: 4 }

En React, las cosas individuales a ser renderizadas dentro de llaves deben ser valores primitivos, como números o strings.

La solución es la siguiente:

const App = () => {
  const friends = [
    { name: 'Peter', age: 4 },
    { name: 'Maya', age: 10 },
  ]

  return (
    <div>
      <p>{friends[0].name} {friends[0].age}</p>
      <p>{friends[1].name} {friends[1].age}</p>
    </div>
  )
}

export default App

Ahora el nombre del amigo es renderizado dentro de las llaves de manera separada.

{friends[0].name}

y la edad

{friends[0].age}

Después de corregir el error, tu deberías limpiar los mensajes de la consola presionando el botón 🚫 y luego recargando el contenido de la página, y asegurarte de que no se están mostrando mensajes de error.

Una pequeña nota adicional a la anterior. React también permite renderizar arreglos si el arreglo contiene valores que son elegibles para renderizar (como números y cadenas). Así que el siguiente programa funcionaría, aunque el resultado puede que no sea el que queremos:

const App = () => {
  const friends = [ 'Peter', 'Maya']

  return (
    <div>
      <p>{friends}</p>
    </div>
  )
}

En esta parte, ni siquiera vale la pena intentar utilizar la renderización directa de las tablas; volveremos a ello en la siguiente parte.