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ón es true, renderiza <Elemento />
  • Si condición es false, 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.