Props - Comunicación entre Componentes
En la clase anterior creamos el componente JobCard y lo usamos manualmente tres veces. Ahora vamos a profundizar en props, el mecanismo fundamental de React para pasar datos entre componentes.
¿Qué son las Props?
Props (abreviatura de “properties”) son argumentos que se pasan a los componentes de React, similar a cómo pasas argumentos a una función.
// Función normal con argumentos
function saludar(nombre) {
return `Hola ${nombre}`
}
saludar('Miguel') // "Hola Miguel"
// Componente React con props
function Saludo({ nombre }) {
return <h1>Hola {nombre}</h1>
}
;<Saludo nombre="Miguel" /> // <h1>Hola Miguel</h1>
Características de las props:
- 📦 Son objetos con los datos que le pasas al componente
- ⬇️ Fluyen de padre a hijo (nunca al revés)
- 🔒 Son inmutables (no se pueden modificar dentro del componente)
- 🔄 Hacen que los componentes sean reutilizables
Pasando Props de Padre a Hijo
Imagina que tienes un componente padre (App) y un componente hijo (JobCard):
// Componente HIJO - recibe props
function JobCard({ title, company }) {
return (
<article>
<h3>{title}</h3>
<p>{company}</p>
</article>
)
}
// Componente PADRE - pasa props
function App() {
return (
<div>
<JobCard title="Frontend Developer" company="TechCorp" />
<JobCard title="Backend Developer" company="DataStack" />
</div>
)
}
El flujo es unidireccional:
App (padre)
│
├─ pasa: title="Frontend Developer"
├─ pasa: company="TechCorp"
│
▼
JobCard (hijo)
│
└─ usa: {title} y {company} para renderizar
Tipos de Valores en Props
Puedes pasar diferentes tipos de datos como props:
Strings (texto)
<JobCard title="Frontend Developer" location="Madrid" />
Numbers (números)
<JobCard salary={45000} experience={3} />
⚠️ Nota: Los números se pasan entre llaves
{}, no entre comillas.
Booleans (booleanos)
;<JobCard isRemote={true} isFeatured={false} />
{
/* Shorthand para true */
}
;<JobCard isRemote isFeatured />
Arrays (listas)
<JobCard tags={['React', 'TypeScript', 'CSS']} />
Objects (objetos)
<JobCard
job={{
title: 'Frontend Developer',
company: 'TechCorp',
salary: 45000,
}}
/>
Functions (funciones)
<JobCard onApply={() => console.log('Aplicando...')} />
Desestructuración de Props
Hay dos formas de recibir props en un componente:
Sin desestructuración (verboso)
function JobCard(props) {
return (
<article>
<h3>{props.title}</h3>
<p>{props.company}</p>
<p>{props.location}</p>
</article>
)
}
Problema: Escribir props. todo el tiempo es repetitivo.
Con desestructuración (recomendado)
function JobCard({ title, company, location }) {
return (
<article>
<h3>{title}</h3>
<p>{company}</p>
<p>{location}</p>
</article>
)
}
Ventajas:
- ✅ Código más limpio y legible
- ✅ Ves inmediatamente qué props usa el componente
- ✅ Menos repetición de código
Desestructuración anidada
Si pasas un objeto como prop, puedes desestructurar dentro del componente:
// Opción 1: Desestructurar dentro del componente
function JobCard({ job }) {
const { title, company, location } = job
return (
<article>
<h3>{title}</h3>
<p>{company}</p>
<p>{location}</p>
</article>
)
}
// Opción 2: Desestructurar en los parámetros
function JobCard({ job: { title, company, location } }) {
return (
<article>
<h3>{title}</h3>
<p>{company}</p>
<p>{location}</p>
</article>
)
}
Recomendación: La opción 1 es más legible.
Props por Defecto
A veces quieres que una prop tenga un valor por defecto si no se proporciona:
function JobCard({ title, company, location = 'Remoto' }) {
return (
<article>
<h3>{title}</h3>
<p>{company}</p>
<p>{location}</p>
</article>
)
}
// Si no pasas location, usará "Remoto"
<JobCard title="Frontend Dev" company="TechCorp" />
// Renderiza: <p>Remoto</p>
// Si pasas location, usa ese valor
<JobCard title="Frontend Dev" company="TechCorp" location="Madrid" />
// Renderiza: <p>Madrid</p>
Props Condicionales
Puedes usar props para mostrar contenido condicionalmente:
function JobCard({ title, company, isRemote, isFeatured }) {
return (
<article className={isFeatured ? 'job-card featured' : 'job-card'}>
<h3>{title}</h3>
<p>{company}</p>
{isRemote && <span className="badge">🏠 Remoto</span>}
{isFeatured && <span className="badge">⭐ Destacado</span>}
</article>
)
}
¿Qué hace &&?
{
condición && <Elemento />
}
- Si
condiciónestrue, renderiza<Elemento /> - Si
condiciónesfalse, no renderiza nada
Ejemplos:
{
true && <span>Se muestra</span>
} // ✅ Se renderiza
{
false && <span>No se muestra</span>
} // ❌ No se renderiza
{
isRemote && <span>Remoto</span>
} // Depende de isRemote
Mejor usar las ternarias
Muchas veces es mejor usar las ternarias para renderizar condicionalmente:
{
isRemote ? <span>Remoto</span> : null
}
¿Por qué? Porque cuando estamos comparando booleanos no hay ningún problema, pero a veces podemos usar valores que no sean booleanos, como strings, números, etc. pero que sean falsy, y se renderizarán en la aplicación.
{
results.length && <span>Remoto</span>
}
Esto mostraría el número 0 en la pantalla, que es un error muy común en aplicaciones de React.
Ejercicio Práctico: Mejorar JobCard
Vamos a mejorar nuestro componente JobCard añadiendo más props:
function JobCard({ job, isRemote, isFeatured, isNew }) {
const { title, company, location, salary, description, tags } = job
return (
<article className={`job-card ${isFeatured ? 'featured' : ''}`}>
<header className="job-card-header">
<h3 className="job-title">{title}</h3>
<p className="job-company">{company}</p>
<div className="badges">
{isNew && <span className="badge new">🆕 Nuevo</span>}
{isFeatured && <span className="badge featured">⭐ Destacado</span>}
{isRemote && <span className="badge remote">🏠 Remoto</span>}
</div>
</header>
<div className="job-card-body">
<p className="job-location">📍 {location}</p>
<p className="job-salary">💰 {salary}</p>
<p className="job-description">{description}</p>
</div>
<footer className="job-card-footer">
<span className="job-tags">{tags.join(', ')}</span>
<button className="btn-apply">Aplicar</button>
</footer>
</article>
)
}
export default JobCard
Y úsalo así en App.jsx:
function App() {
return (
<div className="app">
<Header />
<main>
<section className="jobs-container">
<h2>Trabajos Disponibles (3)</h2>
<div className="jobs-grid">
<JobCard
job={{
title: 'Frontend Developer',
company: 'TechCorp',
location: 'Madrid, España',
salary: '€45,000 - €60,000',
description: 'Buscamos desarrollador frontend con experiencia en React.',
tags: ['React', 'TypeScript', 'CSS'],
}}
isRemote={false}
isFeatured={true}
isNew={true}
/>
<JobCard
job={{
title: 'Backend Developer',
company: 'DataStack',
location: 'Barcelona, España',
salary: '€50,000 - €70,000',
description: 'Desarrollador backend para Node.js y bases de datos.',
tags: ['Node.js', 'PostgreSQL', 'API'],
}}
isRemote={true}
isFeatured={false}
isNew={false}
/>
<JobCard
job={{
title: 'Full Stack Developer',
company: 'StartupX',
location: 'Remoto',
salary: '€40,000 - €55,000',
description: 'Desarrollador full stack para startup en crecimiento.',
tags: ['React', 'Node.js', 'MongoDB'],
}}
isRemote={true}
isFeatured={false}
isNew={true}
/>
</div>
</section>
</main>
<Footer />
</div>
)
}
Props: Reglas Importantes
Las props fluyen de arriba hacia abajo
// ✅ Correcto: Padre → Hijo
function App() {
return <JobCard title="Frontend Dev" />
}
// ❌ Incorrecto: Un hijo NO puede pasar props a su padre directamente
// (Para esto se usan callbacks, que veremos más adelante)
Siempre desestructura en los parámetros
// ❌ Menos legible
function JobCard(props) {
return <h3>{props.title}</h3>
}
// ✅ Más legible
function JobCard({ title }) {
return <h3>{title}</h3>
}
Composición de Componentes
Las props permiten componer componentes más complejos a partir de componentes simples:
// Componente simple: Badge
function Badge({ children, type = 'default' }) {
return <span className={`badge badge-${type}`}>{children}</span>
}
// Componente que usa Badge
function JobCard({ title, isRemote, isFeatured }) {
return (
<article>
<h3>{title}</h3>
{isRemote && <Badge type="info">🏠 Remoto</Badge>}
{isFeatured && <Badge type="success">⭐ Destacado</Badge>}
</article>
)
}
Ventajas:
- ♻️ Componentes reutilizables (Badge se puede usar en otros lugares)
- 🧩 Fácil de mantener (cambiar Badge afecta a todos los lugares donde se usa)
- 📖 Código más legible
Fragments en React
En React te habrás dado cuenta que no puedes retornar múltiples elementos directamente, sino que debes envolverlos en un elemento padre.
Es importante entender Fragments, una característica esencial de React que usarás constantemente.
El problema: Solo un elemento raíz
React tiene una regla: un componente solo puede retornar un elemento raíz.
// ❌ Error: No puedes retornar múltiples elementos
function JobCard() {
return (
<h3>Título</h3>
<p>Descripción</p>
)
}
// Error: Adjacent JSX elements must be wrapped in an enclosing tag
Solución tradicional: Envolver en un <div>:
// ✅ Funciona, pero agrega un div innecesario
function JobCard() {
return (
<div>
<h3>Título</h3>
<p>Descripción</p>
</div>
)
}
Problema: Este <div> extra contamina el HTML y puede romper estilos CSS:
<!-- Resultado en el DOM -->
<div class="jobs-grid">
<div>
<!-- ← div extra innecesario -->
<h3>Título</h3>
<p>Descripción</p>
</div>
<div>
<!-- ← otro div extra -->
<h3>Título 2</h3>
<p>Descripción 2</p>
</div>
</div>
La solución: React Fragment
Fragment es un componente especial que agrupa elementos sin agregar nodos al DOM:
import { Fragment } from 'react'
function JobCard() {
return (
<Fragment>
<h3>Título</h3>
<p>Descripción</p>
</Fragment>
)
}
Resultado en el DOM:
<!-- Sin divs extras! -->
<h3>Título</h3>
<p>Descripción</p>
Sintaxis corta: <></>
React tiene una sintaxis abreviada para Fragment:
function JobCard() {
return (
<>
<h3>Título</h3>
<p>Descripción</p>
</>
)
}
Es lo mismo que <Fragment> pero más corto y limpio.
¿Cuándo usar Fragment?
Caso 1: Retornar múltiples elementos
function JobActions() {
return (
<>
<button>Aplicar</button>
<button>Guardar</button>
<button>Compartir</button>
</>
)
}
Caso 2: Renderizar listas sin wrapper
function JobTags({ tags }) {
return (
<>
{tags.map((tag) => (
<span key={tag} className={styles.tag}>
{tag}
</span>
))}
</>
)
}
Caso 3: Condicionales que retornan múltiples elementos
function JobCard({ job, showDetails }) {
return (
<article className={styles.card}>
<h3>{job.title}</h3>
{showDetails && (
<>
<p>{job.description}</p>
<p>{job.requirements}</p>
<p>{job.benefits}</p>
</>
)}
</article>
)
}
Fragment vs div
Cuándo usar <Fragment> / <></>:
- ✅ No necesitas estilos en el wrapper
- ✅ No quieres alterar la estructura del DOM
- ✅ Usas CSS Grid/Flexbox y un div extra lo rompería
- ✅ Simplemente agrupar elementos lógicamente
Cuándo usar <div>:
- ✅ Necesitas aplicar estilos al wrapper
- ✅ Necesitas event handlers en el wrapper
- ✅ Necesitas una referencia (ref) al wrapper
Ejemplo comparativo:
// ❌ Fragment NO puede tener className
function JobCard() {
return (
<Fragment className={styles.card}>
{/* ERROR */}
<h3>Título</h3>
<p>Subtítulo</p>
</Fragment>
)
}
// ✅ Usa div si necesitas estilos
function JobCard() {
return (
<div className={styles.card}>
<h3>Título</h3>
<p>Subtítulo</p>
</div>
)
}
// ✅ Fragment para agrupar sin estilos
function JobCard() {
return (
<article className={styles.card}>
<h3>Título</h3>
<p>Subtítulo</p>
</article>
)
}
¡Resumiendo que es gerundio!
En esta clase has aprendido:
- 🎁 Props - Argumentos que se pasan a componentes
- ⬇️ Flujo unidireccional - Las props van de padre a hijo
- 🔓 Desestructuración - Forma limpia de recibir props
- 🎯 Valores por defecto - Props con valores predeterminados
- ❓ Renderizado condicional - Mostrar contenido según props
En la próxima clase aprenderemos a renderizar listas con .map() y a importar datos desde un archivo JSON para automatizar la creación de múltiples JobCards.
💡 Recuerda: Las props son el mecanismo fundamental de React para crear componentes reutilizables y componibles. Dominar las props es esencial para ser efectivo con React.