Estado con useState

En la clase anterior aprendimos a manejar eventos, pero los componentes no recordaban nada. Cuando hacías clic en “Aplicar”, se ejecutaba la función pero el botón no cambiaba visualmente. Ahora vamos a solucionar eso con el estado.

¿Qué es el estado?

El estado es la memoria de un componente. Es donde guardas información que puede cambiar con el tiempo.

Analogía: El interruptor de luz

Piénsalo así. Imagina un interruptor de luz:

  • Tiene un estado: encendido o apagado
  • Cuando lo presionas, cambia de estado
  • Y la luz se actualiza automáticamente

Lo mismo pasa con los componentes de React:

  • Tienen un estado (por ejemplo: aplicado = false)
  • Cuando el usuario interactúa, cambias el estado (a aplicado = true)
  • React actualiza la interfaz automáticamente

¿Por qué necesitamos estado?

Hasta ahora, nuestros componentes son estáticos:

function Boton() {
  return <button>Haz clic</button>
}

Este botón siempre dice “Haz clic”. No puede recordar si ya hiciste clic.

Con estado, podemos hacer componentes dinámicos que recuerdan cosas:

function Boton() {
  const [clicks, setClicks] = React.useState(0)

  return <button onClick={() => setClicks(clicks + 1)}>Clicks: {clicks}</button>
}

Ahora el botón recuerda cuántas veces has hecho clic.

Gestionando estado con useState

Vamos a añadir estado a nuestro componente JobCard para que el botón “Aplicar” cambie cuando lo clickeas.

Actualiza tu archivo react.html:

function JobCard({ titulo, empresa, ubicacion, descripcion }) {
  // ¡NOVEDAD! Estado: ¿el usuario aplicó a este empleo?
  const [aplicado, setAplicado] = React.useState(false)

  const handleAplicar = () => {
    setAplicado(true) // Cambiamos el estado a true
  }

  return (
    <article className="job-listing-card">
      <div>
        <h3>{titulo}</h3>
        <small>
          {empresa} | {ubicacion}
        </small>
        <p>{descripcion}</p>
      </div>
      <button
        className={aplicado ? 'button-apply-job is-applied' : 'button-apply-job'}
        onClick={handleAplicar}
        disabled={aplicado}
      >
        {aplicado ? '¡Aplicado!' : 'Aplicar'}
      </button>
    </article>
  )
}

¿Qué cambió?

Analicemos la novedad paso a paso.

useState: El Hook de estado

function JobCard({ titulo, empresa, ubicacion, descripcion }) {
  // Declarar estado
  const [aplicado, setAplicado] = React.useState(false)
  //     ↓          ↓                ↓
  //   valor    función         valor inicial
  //  actual   para cambiar

  const handleAplicar = () => {
    setAplicado(true) // Actualizamos el estado
  }

  return (
    <article className="job-listing-card">
      {/* ... */}
      <button
        className={aplicado ? 'button-apply-job is-applied' : 'button-apply-job'}
        onClick={handleAplicar}
        disabled={aplicado}
      >
        {aplicado ? '¡Aplicado!' : 'Aplicar'}
      </button>
    </article>
  )
}

Desglose de useState

const [aplicado, setAplicado] = React.useState(false)

¿Qué significa esto?

  1. React.useState(false) - Crea una variable de estado con valor inicial false
  2. aplicado - Variable que contiene el valor actual del estado
  3. setAplicado - Función para actualizar el estado

¿Por qué usar array destructuring?

// useState devuelve un array con 2 elementos:
const resultado = React.useState(false)
const aplicado = resultado[0] // El valor
const setAplicado = resultado[1] // La función

// Destructuring hace esto más simple:
const [aplicado, setAplicado] = React.useState(false)

Cómo funciona el flujo

  1. Estado inicial: aplicado = false
  2. Usuario hace clic en el botón
  3. Se ejecuta handleAplicar
  4. Se llama setAplicado(true)
  5. React actualiza aplicado a true
  6. React re-renderiza el componente
  7. El botón muestra “¡Aplicado!”, cambia de estilo y se deshabilita

Todo esto automáticamente

Usando el estado en el JSX

<button
  className={aplicado ? 'button-apply-job is-applied' : 'button-apply-job'}
  disabled={aplicado}
>
  {aplicado ? '¡Aplicado!' : 'Aplicar'}
</button>

Tres cosas cambian según el estado:

  1. className - Si aplicado es true, añade la clase is-applied
  2. disabled - Deshabilita el botón cuando aplicado es true
  3. Texto - Cambia de “Aplicar” a “¡Aplicado!”

Operador ternario

{
  aplicado ? '¡Aplicado!' : 'Aplicar'
}

Esto es un operador ternario:

condición ? siVerdadero : siFalso

Es como un if/else en una línea:

// Con if/else (no funciona en JSX directamente)
if (aplicado) {
  return '¡Aplicado!'
} else {
  return 'Aplicar'
}

// Con operador ternario (funciona en JSX)
aplicado ? '¡Aplicado!' : 'Aplicar'

Comparación completa: Antes vs Ahora

Veamos todo el cambio de JavaScript vanilla a React con useState.

Antes (JavaScript vanilla)

En apply-button.js:

// Seleccionar elemento
const jobsListingSection = document.querySelector('.jobs-listings')

// Añadir evento
jobsListingSection.addEventListener('click', function (event) {
  const element = event.target
  if (element.classList.contains('button-apply-job')) {
    // Actualizar manualmente el DOM
    element.textContent = '¡Aplicado!'
    element.classList.add('is-applied')
    element.disabled = true
  }
})

Problemas:

  • ❌ Manipulación manual del DOM
  • ❌ No hay separación entre datos y UI
  • ❌ Difícil de testear
  • ❌ Propenso a errores
  • ❌ Cada botón necesita lógica especial

Ahora (React con useState)

function JobCard({ titulo, empresa, ubicacion, descripcion }) {
  const [aplicado, setAplicado] = React.useState(false)

  return (
    <button onClick={() => setAplicado(true)} disabled={aplicado}>
      {aplicado ? '¡Aplicado!' : 'Aplicar'}
    </button>
  )
}

Ventajas:

  • ✅ No manipulas el DOM manualmente
  • ✅ El estado y la UI están sincronizados automáticamente
  • ✅ Cada componente tiene su propio estado independiente
  • ✅ Fácil de testear
  • ✅ Declarativo: describes qué quieres, no cómo hacerlo

Cada componente tiene su propio estado

Mira esto: tenemos dos tarjetas de empleo:

<JobCard titulo="Frontend Developer" />
<JobCard titulo="Backend Developer" />

Cada una tiene su propio estado independiente:

  • Si aplicas al primero, solo el primero cambia
  • El segundo mantiene su estado original
  • No se interfieren entre sí

¿Por qué?

Porque cada vez que usas <JobCard />, React crea una instancia nueva con su propio estado.

Reglas de useState

1. Solo en el nivel superior

// ✅ Correcto: en el nivel superior del componente
function JobCard() {
  const [aplicado, setAplicado] = React.useState(false)
  return <button>Aplicar</button>
}

// ❌ Incorrecto: dentro de un if
function JobCard() {
  if (algo) {
    const [aplicado, setAplicado] = React.useState(false) // Error!
  }
  return <button>Aplicar</button>
}

// ❌ Incorrecto: dentro de un loop
function JobCard() {
  for (let i = 0; i < 10; i++) {
    const [aplicado, setAplicado] = React.useState(false) // Error!
  }
  return <button>Aplicar</button>
}

2. Solo en componentes de React

// ✅ Correcto: en un componente
function MiComponente() {
  const [estado, setEstado] = React.useState(0)
}

// ❌ Incorrecto: en una función normal
function miFuncion() {
  const [estado, setEstado] = React.useState(0) // Error!
}

3. Nunca modificar el estado directamente

const [aplicado, setAplicado] = React.useState(false)

// ❌ Incorrecto: modificar directamente
aplicado = true // No funciona y es un error

// ✅ Correcto: usar la función set
setAplicado(true)

Tipos de valores en useState

Puedes usar useState con cualquier tipo de valor:

Booleanos

const [activo, setActivo] = React.useState(false)
const [visible, setVisible] = React.useState(true)

Números

const [contador, setContador] = React.useState(0)
const [edad, setEdad] = React.useState(25)

Strings

const [nombre, setNombre] = React.useState('')
const [mensaje, setMensaje] = React.useState('Hola')

Arrays

const [empleos, setEmpleos] = React.useState([])
const [numeros, setNumeros] = React.useState([1, 2, 3])

Objetos

const [usuario, setUsuario] = React.useState({ nombre: 'Miguel', edad: 25 })
const [config, setConfig] = React.useState({ tema: 'oscuro', idioma: 'es' })

Ejemplo práctico: Contador

Veamos otro ejemplo simple para entender mejor useState:

function Contador() {
  const [count, setCount] = React.useState(0)

  return (
    <div>
      <p>Has hecho clic {count} veces</p>
      <button onClick={() => setCount(count + 1)}>Incrementar</button>
      <button onClick={() => setCount(count - 1)}>Decrementar</button>
      <button onClick={() => setCount(0)}>Resetear</button>
    </div>
  )
}

¿Qué pasa aquí?

  1. Estado inicial: count = 0
  2. Cada clic actualiza el estado
  3. React re-renderiza mostrando el nuevo valor

Múltiples estados en un componente

Puedes tener varios estados en el mismo componente:

function JobCard({ titulo, empresa }) {
  const [aplicado, setAplicado] = React.useState(false)
  const [guardado, setGuardado] = React.useState(false)
  const [likes, setLikes] = React.useState(0)

  return (
    <article>
      <h3>{titulo}</h3>
      <button onClick={() => setAplicado(true)} disabled={aplicado}>
        {aplicado ? '¡Aplicado!' : 'Aplicar'}
      </button>
      <button onClick={() => setGuardado(!guardado)}>
        {guardado ? '❤️ Guardado' : '🤍 Guardar'}
      </button>
      <button onClick={() => setLikes(likes + 1)}>👍 {likes}</button>
    </article>
  )
}

Cada estado es independiente y se puede actualizar por separado.

Actualizando estado basado en el valor anterior

A veces necesitas actualizar el estado basándote en su valor anterior:

function Contador() {
  const [count, setCount] = React.useState(0)

  // ✅ Forma correcta: usando una función
  const incrementar = () => {
    setCount((prevCount) => prevCount + 1)
  }

  // ⚠️ Forma simple: funciona pero puede tener problemas
  const incrementarSimple = () => {
    setCount(count + 1)
  }

  return <button onClick={incrementar}>Incrementar</button>
}

¿Cuándo usar la función?

Cuando necesitas hacer múltiples actualizaciones seguidas o cuando el nuevo valor depende del anterior.

Lo que hemos aprendido

En esta clase has aprendido:

  • 🧠 Qué es el estado - La memoria del componente
  • 🎯 Cómo usar useState - Crear y actualizar estado
  • 🔄 Flujo de actualización - Cómo React re-renderiza componentes
  • 🎨 Estado en JSX - Usar el estado para cambiar la UI
  • Comparación con vanilla JS - Mucho más simple y automático
  • 🔑 Reglas de useState - Dónde y cómo usarlo
  • 📊 Tipos de valores - Booleanos, números, strings, arrays, objetos
  • 🔢 Múltiples estados - Varios useState en un componente

En la próxima clase aprenderemos a renderizar listas de elementos dinámicamente usando .map(), para no tener que escribir cada <JobCard /> manualmente.

💡 Recuerda: El estado es la memoria del componente. Cuando el estado cambia, React re-renderiza automáticamente. Nunca modifiques el estado directamente, siempre usa la función set.