Tema
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:
| HTML | JSX |
|---|---|
class | className |
for | htmlFor |
onclick | onClick |
tabindex | tabIndex |
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 mostrarEstilos 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 ejecutarseLa ú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 Appcss
.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
- 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>- Crea un componente
Productoque reciba un objeto connombre,precioydescuento(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.
stylerecibe un objeto; JSX escapa el contenido y te protege de XSS.