react-hook-usestate

El Hook useState en React

  • 5 min

Hasta ahora, nuestros componentes han sido simples presentadores. Reciben datos (Props) y los pintan. Si el padre cambia las props, el componente se actualizaba.

Pero, ¿qué pasa si el cambio nace dentro del componente? ¿Qué pasa si quiero escribir en un input, desplegar un menú o incrementar un contador al hacer click?

Aquí es donde entra el concepto de Estado interno del componente

El estado es la memoria interna del componente. Son los datos que pertenecen al componente, que cambian con el tiempo y que, cuando cambian, obligan a la interfaz a redibujarse.

Para gestionar esto en componentes funcionales, utilizamos nuestro primer Hook: useState.

Los Hooks son funciones especiales que permiten a tu componente funcional (efímero) “engancharse” a funcionalidades de React (persistentes, como el estado o el ciclo de vida).

Por convención, todos los Hooks comienzan con el prefijo use (ej: useState, useEffect, useContext).

El problema de las variables locales

Para entender por qué necesitamos useState, primero vamos a “equivocarnos” intentándolo “a la vieja usanza”, con variables normales de JavaScript.

Mirad este código, parece lógico, ¿verdad?

export default function ContadorRoto() {
  let cuenta = 0; // Variable local

  const incrementar = () => {
    cuenta = cuenta + 1;
    console.log(cuenta); // En consola veremos 1, 2, 3...
  };

  return (
    <div>
      <h1>{cuenta}</h1>
      <button onClick={incrementar}>+1</button>
    </div>
  );
}
Copied!

Si ejecutáis esto, veréis que

  • ✔️El console.log muestra que la variable sube
  • ❌ Pero el número en la pantalla (el <h1>) se queda clavado en 0

Por dos razones fundamentales de la arquitectura de React:

  1. Falta de Reactividad: React no tiene forma de saber que cuenta ha cambiado. Modificar una variable local no avisa a React para que dispare un re-renderizado.
  2. Alcance de la variable: Incluso si forzáramos el renderizado, al volverse a ejecutar la función ContadorRoto(), la línea let cuenta = 0 se ejecutaría de nuevo, reiniciando todo.

La solución: useState

Para solucionar ambos problemas, tenemos el Hook useState. Es una función que nos proporciona una variable que:

  1. Persiste entre renderizados (React se encarga de “recordarla”)
  2. Dispara una actualización de la interfaz cuando se modifica

Para usarlo, primero lo importamos de react,

import { useState } from 'react';
Copied!

Y lo utilizamos así:

const [estado, setEstado] = useState(valorInicial);
Copied!

Lo que ocurre aquí es Destructuring de Arrays. La función useState devuelve siempre un array con exactamente dos elementos:

  1. El valor actual del estado (inicialmente valorInicial).
  2. Una función setter para actualizar ese valor.

El contador que sí funciona

Vamos a arreglar el ejemplo anterior:

import { useState } from 'react';

export default function Contador() {
  // Declaramos una variable de estado llamada "cuenta"
  const [cuenta, setCuenta] = useState(0);

  const incrementar = () => {
    // Usamos el setter, NO modificamos la variable directamente
    setCuenta(cuenta + 1);
  };

  return (
    <div>
      <h1>{cuenta}</h1>
      <button onClick={incrementar}>+1</button>
    </div>
  );
}
Copied!

Ahora, al llamar a setCuenta(1):

React actualiza su memoria interna: “cuenta ahora vale 1”.

React detecta el cambio y vuelve a ejecutar (re-renderiza) la función Contador.

En esta nueva ejecución, useState(0) ya no devuelve 0, sino 1 (el valor recordado).

El JSX usa este valor, y devuelve el HTML <h1>1</h1>.

React actualiza el DOM.

Reglas de uso

  1. Nunca modifiquéis el estado directamente: cuenta = 5 no hará nada. Debéis usar setCuenta(5).
  2. Los Hooks solo en el nivel superior: Nunca llaméis a useState dentro de bucles, condiciones (if) o funciones anidadas. React depende del orden de llamada de los hooks para saber qué estado corresponde a qué variable.

No todo necesita estar en el estado. Preguntaos:

  • ¿Este dato se pasa por props desde el padre? ➡ No es estado.
  • ¿Se mantiene igual con el tiempo? ➡ No es estado.
  • ¿Se puede calcular a partir de otros estados o props? ➡ No es estado (es una variable derivada).

Solo usad useState para datos que cambian con el tiempo y que necesitáis recordar entre renderizados para pintar la interfaz.

Actualizaciones funcionales

Las actualizaciones de estado en React pueden ser asíncronas. Si intentáis actualizar el estado basándoos en el valor anterior, hacer esto puede ser peligroso en escenarios rápidos:

// Si pulsamos muy rápido o dentro de bucles
setCuenta(cuenta + 1);
setCuenta(cuenta + 1);
setCuenta(cuenta + 1);
Copied!

Es posible que, debido al cierre (closure) de JavaScript, en las tres líneas cuenta valga lo mismo (ej: 0), y el resultado final sea 1 en vez de 3.

Para evitar esto, cuando el nuevo estado depende del anterior, debemos pasar una función callback al setter:

// Forma correcta y segura (Functional Update)
setCuenta((valorAnterior) => valorAnterior + 1);
Copied!

Aquí React nos garantiza que valorAnterior es el valor más reciente y real que tiene en memoria, justo antes de aplicar el cambio.

Estado con Objetos

El hook useState no mezcla (merge) automáticamente objetos. Es decir, si tenéis un objeto complejo:

const [usuario, setUsuario] = useState({
  nombre: 'Luis',
  edad: 30,
  email: '[email protected]'
});
Copied!

Y queréis cambiar solo el email, debéis copiar manualmente el resto de propiedades, o las perderéis.

// ❌ MAL: Esto borra nombre y edad
setUsuario({ email: '[email protected]' });

// ✅ BIEN: Usamos el Spread Operator para copiar lo anterior
setUsuario({ 
  ...usuario, // Copia nombre y edad
  email: '[email protected]' // Sobrescribe email
});
Copied!

Si el estado tiene muchas propiedades no relacionadas, suele ser mejor dividirlas en múltiples useState independientes (const [nombre, setNombre], const [edad, setEdad]) en lugar de tener un objeto gigante.