Skip to content

JSX ​

JSX es la sintaxis que usa React para describir la interfaz. Parece HTML, pero es JavaScript con superpoderes: puedes meter variables, expresiones y lógica directamente en el marcado. Dominarla es el primer paso para escribir componentes con fluidez.

Concepto ​

JSX no es HTML ni un string: es azúcar sintáctico que Vite convierte en llamadas a funciones de JavaScript.

jsx
// Lo que escribes
const elemento = <h1 className="titulo">Hola</h1>

// Lo que el navegador recibe (simplificado)
const elemento = jsx('h1', { className: 'titulo', children: 'Hola' })

El resultado es un objeto que describe qué dibujar. Por eso puedes guardar JSX en variables, devolverlo de funciones o meterlo en arreglos.

Reglas de JSX ​

1. Un solo elemento raíz ​

Un componente devuelve un elemento. Si necesitas varios, envuélvelos en un Fragment (<>...</>), que no agrega nada al DOM:

jsx
// ❌ Error: dos elementos raíz
function Malo() {
  return (
    <h1>Título</h1>
    <p>Párrafo</p>
  )
}

// ✅ Fragment
function Bueno() {
  return (
    <>
      <h1>Título</h1>
      <p>Párrafo</p>
    </>
  )
}

2. Todas las etiquetas se cierran ​

jsx
<img src="foto.jpg" alt="Foto" />
<input type="text" />
<br />

3. Atributos en camelCase ​

Como JSX es JavaScript, algunos nombres cambian:

HTMLJSX
classclassName
forhtmlFor
onclickonClick
tabindextabIndex
style="color: red"style recibe un objeto (ver más abajo)
<!-- comentario -->{/* comentario */}

Los atributos aria-* y data-* se escriben igual que en HTML.

Expresiones con llaves ​

Dentro de JSX, las llaves simples abren una "ventana" a JavaScript. Puedes poner cualquier expresión (algo que produce un valor):

jsx
function Perfil() {
  const usuario = { nombre: 'Ana López', edad: 28, avatar: 'https://i.pravatar.cc/100?img=5' }
  const hoy = new Date()

  return (
    <article>
      {/* Variables */}
      <h2>{usuario.nombre}</h2>

      {/* Operaciones */}
      <p>En 5 años tendrá {usuario.edad + 5} años</p>

      {/* Llamadas a funciones y métodos */}
      <p>{usuario.nombre.toUpperCase()}</p>
      <p>Hoy es {hoy.toLocaleDateString('es-MX', { weekday: 'long' })}</p>

      {/* Ternarios */}
      <p>{usuario.edad >= 18 ? 'Mayor de edad' : 'Menor de edad'}</p>

      {/* En atributos: sin comillas alrededor de las llaves */}
      <img src={usuario.avatar} alt={`Avatar de ${usuario.nombre}`} width={100} />
    </article>
  )
}

Expresiones, no sentencias

No puedes usar if, for ni const dentro de las llaves, porque son sentencias, no expresiones. Usa ternarios, && y map, o calcula antes del return.

Qué se muestra y qué no ​

jsx
<p>{'texto'}</p>        // texto
<p>{42}</p>             // 42
<p>{[1, 2, 3]}</p>      // 123 (los arreglos se muestran unidos)
<p>{true}</p>           // (nada)
<p>{null}</p>           // (nada)
<p>{undefined}</p>      // (nada)
<p>{0}</p>              // 0  ← ¡cuidado con esto en condicionales!
<p>{{ a: 1 }}</p>       // ❌ Error: los objetos no se pueden mostrar

Estilos en línea ​

style recibe un objeto con propiedades en camelCase. Por eso verás dobles llaves: las de fuera abren JavaScript y las de dentro son el objeto.

jsx
function Alerta({ tipo }) {
  const colores = { exito: '#16a34a', error: '#dc2626' }

  return (
    <div
      style={{
        backgroundColor: colores[tipo],
        color: 'white',
        padding: '12px 16px',
        borderRadius: 8,          // número = píxeles
        fontWeight: 600
      }}
    >
      {tipo === 'exito' ? '¡Guardado!' : 'Algo salió mal'}
    </div>
  )
}

Para estilos reales usarás clases (className), CSS Modules o Tailwind (lección de estilos). El style en línea es útil para valores dinámicos (un ancho calculado, un color elegido por el usuario).

Clases dinámicas ​

jsx
function Boton({ variante = 'primario', grande, children }) {
  // Construye la lista de clases según las props
  const clases = ['btn', `btn-${variante}`, grande && 'btn-grande'].filter(Boolean).join(' ')
  return <button className={clases}>{children}</button>
}

// <Boton variante="peligro" grande>Eliminar</Boton>
// → <button class="btn btn-peligro btn-grande">Eliminar</button>

JSX en variables y funciones ​

jsx
function Tarjeta({ producto }) {
  // Calcula partes de la UI antes del return
  let etiqueta = null
  if (producto.stock === 0) {
    etiqueta = <span className="agotado">Agotado</span>
  } else if (producto.stock < 5) {
    etiqueta = <span className="pocas">¡Últimas {producto.stock}!</span>
  }

  const precio = new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(
    producto.precio
  )

  return (
    <div className="tarjeta">
      <h3>{producto.nombre}</h3>
      <p>{precio}</p>
      {etiqueta}
    </div>
  )
}

Seguridad: JSX escapa el contenido ​

Si un usuario escribe <script>alert('hack')</script> como su nombre, JSX lo muestra como texto, no lo ejecuta. Esto te protege contra XSS por defecto.

jsx
const comentario = '<img src=x onerror=alert(1)>'
return <p>{comentario}</p> // se ve el texto literal, sin ejecutarse

La única forma de insertar HTML crudo es dangerouslySetInnerHTML, y su nombre es una advertencia: úsalo sólo con contenido confiable o sanitizado (por ejemplo con DOMPurify).

Caso real: tarjeta de perfil ​

jsx
import './App.css'

const perfil = {
  nombre: 'Laura Méndez',
  puesto: 'Desarrolladora Frontend',
  ciudad: 'Guadalajara',
  avatar: 'https://i.pravatar.cc/150?img=47',
  disponible: true,
  habilidades: ['React', 'TypeScript', 'Tailwind', 'Node.js'],
  redes: { github: 'https://github.com', linkedin: 'https://linkedin.com' }
}

function App() {
  const { nombre, puesto, ciudad, avatar, disponible, habilidades, redes } = perfil

  return (
    <article className="perfil">
      <img className="perfil__foto" src={avatar} alt={`Foto de ${nombre}`} />
      <h1>{nombre}</h1>
      <p className="perfil__puesto">
        {puesto} · {ciudad}
      </p>

      <span className={`estado ${disponible ? 'estado--si' : 'estado--no'}`}>
        {disponible ? '● Disponible para proyectos' : '● Sin disponibilidad'}
      </span>

      <ul className="perfil__habilidades">
        {habilidades.map((h) => (
          <li key={h}>{h}</li>
        ))}
      </ul>

      <footer>
        <a href={redes.github} target="_blank" rel="noopener noreferrer">GitHub</a>
        {' · '}
        <a href={redes.linkedin} target="_blank" rel="noopener noreferrer">LinkedIn</a>
      </footer>
    </article>
  )
}

export default App
css
.perfil {
  max-width: 360px;
  margin: 3rem auto;
  padding: 2rem;
  text-align: center;
  font-family: system-ui, sans-serif;
  border-radius: 16px;
  box-shadow: 0 10px 30px rgb(0 0 0 / 0.1);
}
.perfil__foto { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; }
.perfil__puesto { color: #666; }
.estado { display: inline-block; font-size: 0.85rem; padding: 4px 10px; border-radius: 999px; }
.estado--si { background: #dcfce7; color: #166534; }
.estado--no { background: #fee2e2; color: #991b1b; }
.perfil__habilidades {
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
  list-style: none; padding: 0; margin: 1.5rem 0;
}
.perfil__habilidades li { background: #ede9fe; color: #5b21b6; padding: 4px 12px; border-radius: 6px; }

Buena práctica

Mantén el JSX limpio: si una expresión dentro de las llaves se vuelve larga, calcúlala en una variable antes del return con un nombre descriptivo.

Error común

Escribir class en lugar de className o for en lugar de htmlFor. Funciona a medias y React muestra una advertencia en consola. Revisa siempre la consola del navegador.

Ejercicios ​

  1. Convierte este HTML a JSX válido:
html
<div class="caja" onclick="saludar()">
  <label for="email">Email</label>
  <input type="email" id="email" tabindex="1">
  <!-- comentario -->
  <p style="color: blue; font-size: 20px">Hola</p>
  <br>
</div>
  1. Crea un componente Producto que reciba un objeto con nombre, precio y descuento (porcentaje). Si hay descuento, muestra el precio original tachado y el precio final; si no, sólo el precio.
Ver solución
jsx
// 1
function Caja() {
  const saludar = () => alert('Hola')
  return (
    <div className="caja" onClick={saludar}>
      <label htmlFor="email">Email</label>
      <input type="email" id="email" tabIndex={1} />
      {/* comentario */}
      <p style={{ color: 'blue', fontSize: 20 }}>Hola</p>
      <br />
    </div>
  )
}
jsx
// 2
const formato = (n) =>
  new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(n)

function Producto({ producto }) {
  const { nombre, precio, descuento = 0 } = producto
  const final = precio * (1 - descuento / 100)

  return (
    <div>
      <h3>{nombre}</h3>
      {descuento > 0 ? (
        <p>
          <s>{formato(precio)}</s> <strong>{formato(final)}</strong> (-{descuento}%)
        </p>
      ) : (
        <p>{formato(precio)}</p>
      )}
    </div>
  )
}

// Uso: <Producto producto={{ nombre: 'Tenis', precio: 1200, descuento: 25 }} />

Resumen ​

  • JSX se compila a funciones de JavaScript que devuelven objetos.
  • Un solo elemento raíz (usa Fragments <>...</>), etiquetas cerradas y atributos en camelCase.
  • Las llaves insertan expresiones de JavaScript en el contenido y en los atributos.
  • style recibe un objeto; JSX escapa el contenido y te protege de XSS.

Academia Web — aprende, copia y construye.