Creando JobCard: Nuestro primer componente real
Hasta ahora hemos aprendido qué es JSX, qué son los componentes y cómo se diferencian de las funciones normales. Ahora es el momento de aplicar todo ese conocimiento creando un componente real para nuestra aplicación DevJobs.
Componente JobCard: Caso real
Vamos a crear un componente que muestre una tarjeta de empleo. Este componente será reutilizable y recibirá diferentes datos cada vez que lo usemos.
Actualiza tu archivo react.html con este código completo:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React - JobCard</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div id="root"></div>
<script type="module">
import React from 'https://esm.sh/react?dev'
import ReactDOM from 'https://esm.sh/react-dom/client?dev'
window.React = React
window.ReactDOMClient = ReactDOM
</script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// Componente JobCard con múltiples props
function JobCard({ titulo, empresa, ubicacion, descripcion }) {
return (
<article className="job-listing-card">
<div>
<h3>{titulo}</h3>
<small>
{empresa} | {ubicacion}
</small>
<p>{descripcion}</p>
</div>
<button className="button-apply-job">Aplicar</button>
</article>
)
}
// Componente App
function App() {
return (
<section>
<h2>Empleos disponibles</h2>
<div className="jobs-listings">
<JobCard
titulo="Desarrollador Frontend"
empresa="Tech Solutions Inc."
ubicacion="Remoto"
descripcion="Buscamos un desarrollador frontend con experiencia en React, TypeScript y Tailwind CSS."
/>
<JobCard
titulo="Ingeniero de Software Senior"
empresa="Data Driven Co."
ubicacion="Ciudad de México"
descripcion="Estamos buscando un ingeniero de software con experiencia en desarrollo web."
/>
</div>
</section>
)
}
// Renderizar
window.onload = () => {
const rootEl = document.querySelector('#root')
const root = ReactDOMClient.createRoot(rootEl)
root.render(<App />)
}
</script>
</body>
</html>
¿Qué está pasando aquí?
Vamos a analizar cada parte del código en detalle.
1. Componente con múltiples props
function JobCard({ titulo, empresa, ubicacion, descripcion }) {
return (
<article className="job-listing-card">
<h3>{titulo}</h3>
<small>
{empresa} | {ubicacion}
</small>
<p>{descripcion}</p>
<button>Aplicar</button>
</article>
)
}
Este componente:
- Recibe 4 props diferentes mediante destructuring:
{ titulo, empresa, ubicacion, descripcion } - Usa
classNameen lugar declass(porque JSX es JavaScript) - Inserta cada prop en su lugar con
{} - Retorna una estructura JSX completa que representa una tarjeta de empleo
¿Por qué destructuring?
En lugar de escribir props.titulo, props.empresa, etc., usamos destructuring para extraer las props directamente:
// Sin destructuring (más verboso)
function JobCard(props) {
return (
<article>
<h3>{props.titulo}</h3>
<small>{props.empresa}</small>
</article>
)
}
// Con destructuring (más limpio)
function JobCard({ titulo, empresa }) {
return (
<article>
<h3>{titulo}</h3>
<small>{empresa}</small>
</article>
)
}
2. Componente App que compone otros componentes
function App() {
return (
<section>
<h2>Empleos disponibles</h2>
<div className="jobs-listings">
<JobCard
titulo="Desarrollador Frontend"
empresa="Tech Solutions Inc."
ubicacion="Remoto"
descripcion="..."
/>
<JobCard
titulo="Ingeniero de Software Senior"
empresa="Data Driven Co."
ubicacion="Ciudad de México"
descripcion="..."
/>
</div>
</section>
)
}
Aquí vemos la composición de componentes:
Appes un componente que contiene otros componentes- Pasamos props diferentes a cada instancia de
<JobCard /> - Cada instancia renderiza con sus propios datos
- Esta es la composición de componentes en acción
Ventajas de la composición:
- 🔄 Reutilización - El mismo componente, diferentes datos
- 📦 Organización - Cada componente tiene su responsabilidad
- 🧩 Escalabilidad - Fácil añadir más tarjetas
3. Renderizamos el componente principal
root.render(<App />)
Solo renderizamos App, y React automáticamente renderiza todos los componentes hijos. No necesitamos renderizar cada JobCard individualmente.
Comparación con JavaScript vanilla
Veamos cómo hemos mejorado nuestro código comparado con lo que hacíamos antes con JavaScript puro.
Antes (JavaScript vanilla)
En nuestro archivo fetch-data.js teníamos que hacer esto:
jobs.forEach((job) => {
const article = document.createElement('article')
article.className = 'job-listing-card'
article.innerHTML = `<div>
<h3>${job.titulo}</h3>
<small>${job.empresa} | ${job.ubicacion}</small>
<p>${job.descripcion}</p>
</div>
<button class="button-apply-job">Aplicar</button>`
container.appendChild(article)
})
Problemas con este enfoque:
- ❌ Código repetitivo - Cada vez que necesitamos una tarjeta, escribimos todo esto
- ❌ Difícil de mantener - Si queremos cambiar la estructura, hay que buscar en todo el código
- ❌ Manipulación manual del DOM - Tenemos que crear, modificar y añadir elementos manualmente
- ❌ Propenso a errores - Un error de tipeo en el HTML string rompe todo
- ❌ HTML en strings - No hay validación, autocompletado ni resaltado de sintaxis
- ❌ Difícil de testear - Necesitamos un DOM real para probarlo
Ahora (React con componentes)
function JobCard({ titulo, empresa, ubicacion, descripcion }) {
return (
<article className="job-listing-card">
<div>
<h3>{titulo}</h3>
<small>
{empresa} | {ubicacion}
</small>
<p>{descripcion}</p>
</div>
<button className="button-apply-job">Aplicar</button>
</article>
)
}
/* Uso:
<JobCard
titulo={job.titulo}
empresa={job.empresa}
ubicacion={job.ubicacion}
descripcion={job.descripcion}
/>
*/
Ventajas de React:
- ✅ Código limpio y declarativo - Describes qué quieres, no cómo hacerlo
- ✅ Fácil de mantener - Cambias el componente una vez, se actualiza en todos lados
- ✅ React gestiona el DOM - No te preocupas por createElement, appendChild, etc.
- ✅ Reutilizable - Usa el mismo componente con diferentes datos
- ✅ JSX valida la estructura - Errores de sintaxis se detectan inmediatamente
- ✅ Fácil de testear - Puedes testear el componente sin un DOM real
¡Mucho mejor! No necesitas createElement, innerHTML ni appendChild.
Composición de componentes
La verdadera potencia de React está en componer componentes más pequeños para crear interfaces complejas.
Ejemplo: Estructura completa de la aplicación
function Header() {
return (
<header>
<h1>DevJobs</h1>
<nav>
<a href="/">Inicio</a>
<a href="/empleos">Empleos</a>
</nav>
</header>
)
}
function JobList() {
return (
<div className="jobs-listings">
<JobCard titulo="..." empresa="..." ubicacion="..." descripcion="..." />
<JobCard titulo="..." empresa="..." ubicacion="..." descripcion="..." />
</div>
)
}
function Footer() {
return (
<footer>
<p>© 2025 DevJobs</p>
</footer>
)
}
function App() {
return (
<div>
<Header />
<JobList />
<Footer />
</div>
)
}
Principio de responsabilidad única:
Cada componente tiene una responsabilidad específica:
Header- Muestra la cabecera y navegaciónJobList- Muestra la lista de empleosFooter- Muestra el pie de páginaApp- Compone todos los componentes anteriores
Esto hace que el código sea:
- 📖 Más fácil de leer - Cada componente es pequeño y simple
- 🔧 Más fácil de mantener - Cambios localizados en un solo componente
- 🧪 Más fácil de testear - Puedes testear cada componente por separado
- 🔄 Más fácil de reutilizar - Puedes usar
Headeren diferentes páginas
Pasando props dinámicas
Ahora vamos a hacer nuestro componente más interesante usando datos dinámicos:
function App() {
// Array de empleos
const empleos = [
{
id: 1,
titulo: 'Desarrollador Frontend',
empresa: 'Tech Solutions Inc.',
ubicacion: 'Remoto',
descripcion: 'Buscamos un desarrollador frontend con experiencia en React.',
},
{
id: 2,
titulo: 'Ingeniero Backend',
empresa: 'Data Systems',
ubicacion: 'Madrid',
descripcion: 'Experiencia con Node.js y bases de datos.',
},
{
id: 3,
titulo: 'Full Stack Developer',
empresa: 'StartupCo',
ubicacion: 'Barcelona',
descripcion: 'Desarrollo completo de aplicaciones web.',
},
]
return (
<section>
<h2>Empleos disponibles: {empleos.length}</h2>
<div className="jobs-listings">
<JobCard
titulo={empleos[0].titulo}
empresa={empleos[0].empresa}
ubicacion={empleos[0].ubicacion}
descripcion={empleos[0].descripcion}
/>
<JobCard
titulo={empleos[1].titulo}
empresa={empleos[1].empresa}
ubicacion={empleos[1].ubicacion}
descripcion={empleos[1].descripcion}
/>
<JobCard
titulo={empleos[2].titulo}
empresa={empleos[2].empresa}
ubicacion={empleos[2].ubicacion}
descripcion={empleos[2].descripcion}
/>
</div>
</section>
)
}
Ahora estamos usando datos reales en lugar de strings hardcodeados. En la próxima clase veremos cómo hacer esto de forma más elegante con .map().
Ejercicio práctico
Prueba a añadir más props al componente JobCard:
function JobCard({ titulo, empresa, ubicacion, descripcion, salario, modalidad, nivel }) {
return (
<article className="job-listing-card">
<div>
<h3>{titulo}</h3>
<small>
{empresa} | {ubicacion}
</small>
<p>{descripcion}</p>
<div className="job-details">
<span>💰 {salario}</span>
<span>📍 {modalidad}</span>
<span>📊 {nivel}</span>
</div>
</div>
<button className="button-apply-job">Aplicar</button>
</article>
)
}
/* Uso
<JobCard
titulo="Desarrollador Frontend"
empresa="Tech Solutions Inc."
ubicacion="Remoto"
descripcion="Buscamos un desarrollador frontend..."
salario="45.000€ - 60.000€"
modalidad="Remoto"
nivel="Senior"
/>
*/
Buenas prácticas con componentes
1. Un componente, una responsabilidad
Cada componente debe hacer una sola cosa y hacerla bien:
// ✅ Bien: componentes enfocados
function JobTitle({ children }) {
return <h3>{children}</h3>
}
function JobCompany({ empresa, ubicacion }) {
return (
<small>
{empresa} | {ubicacion}
</small>
)
}
// ❌ Mal: componente que hace demasiado
function EverythingComponent({ titulo, empresa, ubicacion, descripcion, usuario, configuracion }) {
// Demasiadas responsabilidades
}
2. Nombres descriptivos
Usa nombres que describan claramente qué hace el componente:
// ✅ Bien
function UserProfileCard() {}
function JobListingItem() {}
function NavigationMenu() {}
// ❌ Mal
function Item() {} // Demasiado genérico
function Thing() {} // No descriptivo
function Comp1() {} // Nada claro
3. Props claras y descriptivas
// ✅ Bien: props con nombres claros
function Button({ text, onClick, disabled }) {
return (
<button onClick={onClick} disabled={disabled}>
{text}
</button>
)
}
// ❌ Mal: props ambiguas
function Button({ t, oc, d }) {
return (
<button onClick={oc} disabled={d}>
{t}
</button>
)
}
Lo que hemos aprendido
- 🎯 Crear un componente real con múltiples props
- 🧩 Composición de componentes - Combinar componentes para crear UIs
- 📊 Comparación con vanilla JS - Las ventajas de usar React
- 🔄 Reutilización - El mismo componente con diferentes datos
- 📦 Organización del código - Cada componente con su responsabilidad
- ✨ Buenas prácticas - Nombres descriptivos, responsabilidad única
En la próxima clase aprenderemos sobre el estado con useState, que nos permitirá crear componentes interactivos que responden a las acciones del usuario.
💡 Recuerda: Los componentes son bloques de construcción reutilizables. Divide tu UI en componentes pequeños y composables para crear aplicaciones más mantenibles.