Datos reactivos — useState
useState es el Hook más importante de React. En este capítulo entenderás el mecanismo de gestión de estado de React e implementarás el filtrado por categorías.
¿En qué se diferencia la «reactividad» de React de la de Vue3?
Vue3 usa ref/reactive para crear datos reactivos; cuando los datos cambian, la vista se actualiza automáticamente.
El enfoque de React es diferente:Cuando modificas el state, React vuelve a ejecutar la función del componente con el nuevo state y genera una nueva vista.。
De ahí proviene el nombre de React: los componentes «reaccionan» a los cambios de state.
Uso básico de useState
useStateEs el Hook más básico de React, se utiliza para añadir estado a los componentes de función.
Ejemplo
import { useState } from 'react'
function Counter() {
// useState(valorInicial) devuelve un array: [valorActual, funciónActualizadora]
const [count, setCount] = useState(0)
function handleClick() {
setCount(count + 1) // Al actualizar el estado, React re-renderiza el componente automáticamente
}
return (
<div>
<p>Número de clics:{count}</p>
<button onClick={handleClick}>+1</button>
</div>
)
}
Cada vez que haces clic en el botón,setCountse actualiza el valor de count; React vuelve a ejecutar la función Counter con el nuevo valor, devuelve un nuevo JSX y la página se actualiza automáticamente.
La convención de React: los nombres de estado se obtienen por
[something, setSomething]desestructuración. Las funciones que comienzan con set son una convención de nomenclatura establecida, no es un requisito estricto, pero toda la comunidad escribe así.
¿Por qué no se puede modificar el state directamente?
En React,el state es de solo lectura. Debe actualizarse mediante la función setter, no se puede modificar directamente.
Ejemplo
function ArticleManager() {
const [title, setTitle] = useState('Título predeterminado')
// Forma incorrecta: modificar directamente
// title = 'Nuevo título' // React no sabe que lo cambiaste, no re-renderiza
// Forma correcta: actualizar mediante el setter
function updateTitle() {
setTitle('Nuevo título') // React sabe que el estado cambió y re-renderiza
}
return (
<div>
<p>{title}</p>
<button onClick={updateTitle}>Cambiar título</button>
</div>
)
}
Detrás de esto está elprincipio de inmutabilidad (Immutable)de React: no modificar los datos antiguos, sino reemplazarlos por datos nuevos.
La forma correcta de actualizar arrays y objetos
Para arrays y objetos, no se puede usar push, splice o modificar propiedades directamente; hay que crear una copia nueva.
Ejemplo
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Aprender React', done: false },
{ id: 2, text: 'Escribir un blog', done: true }
])
// Añadir un elemento: expandir el array anterior + elemento nuevo
function addTodo(text) {
const newTodo = { id: Date.now(), text, done: false }
setTodos([...todos, newTodo]) // Crear un nuevo array con el operador de propagación
}
// Modificar un elemento: recorrer con map, encontrar el que se quiere cambiar y devolver un nuevo objeto
function toggleTodo(id) {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
))
}
// Eliminar un elemento: usar filter para descartar el que no se quiere
function removeTodo(id) {
setTodos(todos.filter(todo => todo.id !== id))
}
return (
<ul>
{todos.map(todo => (
<li key={todo.id} onClick={() => toggleTodo(todo.id)}>
{todo.done ? <s>{todo.text}</s> : todo.text}
</li>
))}
</ul>
)
}
| Operación | Forma incorrecta | Forma correcta |
|---|---|---|
| Añadir | arr.push(item) | setArr([...arr, item]) |
| Modificar | arr[0] = newVal | setArr(arr.map(el => el.id === id ? ... : el)) |
| Eliminar | arr.splice(i, 1) | setArr(arr.filter(el => el.id !== id)) |
| Modificar propiedades de un objeto | obj.name = 'xxx' | setObj({ ...obj, name: 'xxx' }) |
Principio clave: siempre reemplaza el array/objeto antiguo por uno nuevo. El operador de propagación
...y los métodos de array que ya aprendiste (map, filter) son los mejores aliados para las actualizaciones inmutables.
useMemo — valores derivados
useMemoSe utiliza para cachear resultados de cálculos. Cuando las dependencias no cambian, se omite el recálculo para evitar costos de rendimiento innecesarios.
Ejemplo
function ArticleFilter() {
const [articles] = useState([
{ id: 1, title: 'Introducción a React', category: 'React' },
{ id: 2, title: 'Explicación detallada de Promise', category: 'JavaScript' },
{ id: 3, title: 'Diseño de Grid', category: 'CSS' },
{ id: 4, title: 'React Hooks', category: 'React' }
])
const [activeCategory, setActiveCategory] = useState('Todos')
// useMemo cachea el resultado del filtro; solo recalcula cuando cambian las dependencias
const filteredArticles = useMemo(() => {
console.log('Recalculando el resultado del filtro') // Verificar el efecto de la caché
if (activeCategory === 'Todos') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory]) // Array de dependencias
// Usar useMemo para calcular las estadísticas
const totalCount = useMemo(() => articles.length, [articles])
const filteredCount = useMemo(() => filteredArticles.length, [filteredArticles])
return (
<div>
<p>común{totalCount}artículos, actualmente{filteredCount}artículo</p>
<button onClick={() => setActiveCategory('Todos')}>Todos</button>
<button onClick={() => setActiveCategory('React')}>React</button>
<button onClick={() => setActiveCategory('JavaScript')}>JavaScript</button>
<button onClick={() => setActiveCategory('CSS')}>CSS</button>
{filteredArticles.map(a => (
<div key={a.id}><h3>{a.title}</h3><span>{a.category}</span></div>
))}
</div>
)
}
El array de dependencias de useMemo(fn, deps) debe completarse: todas las variables internas del componente utilizadas en la función deben declararse en el array. React usa la comparación superficial para decidir si recalcular.
Manos a la obra: añade un filtro de categorías al blog
Ejemplo
import { useState, useMemo } from 'react'
import './App.css'
function App() {
const [articles] = useState([
{ id: 1, title: 'Guía completa para comenzar con React', summary: 'Aprende React desde cero', category: 'React', date: '2024-05-10' },
{ id: 2, title: 'Explicación detallada de la programación asíncrona en JS', summary: 'Entiende Promise y async/await', category: 'JavaScript', date: '2024-05-08' },
{ id: 3, title: 'Práctica de CSS Grid Layout', summary: 'Implementación de diseño reactivo con Grid', category: 'CSS', date: '2024-05-05' },
{ id: 4, title: 'React Hooks en profundidad', summary: 'Entendiendo a fondo useState y useEffect', category: 'React', date: '2024-05-03' },
{ id: 5, title: 'Guía completa de Flexbox', summary: 'Aprende flexbox (diseño flexible) en un artículo', category: 'CSS', date: '2024-05-01' },
])
const [activeCategory, setActiveCategory] = useState('Todos')
// Extraer todas las categorías (sin duplicados)
const categories = useMemo(() => {
const cats = articles.map(a => a.category)
return ['Todos', ...new Set(cats)]
}, [articles])
// Filtrar los artículos según la categoría
const filteredArticles = useMemo(() => {
if (activeCategory === 'Todos') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory])
return (
<div className="app">
<header className="navbar">
<h1 className="logo">EJEMPLO Blog</h1>
<nav>
<a href="/">Inicio</a>
<a href="#">Acerca de</a>
</nav>
</header>
<main className="container">
<h2 className="section-title">Artículos recientes</h2>
{/* Grupo de botones de filtro de categorías */}
<div className="category-bar">
{categories.map(cat => (
<button
key={cat}
className={activeCategory === cat ? 'active' : ''}
onClick={() => setActiveCategory(cat)}
>
{cat}
</button>
))}
</div>
<p className="result-info">común{filteredArticles.length}artículo</p>
{filteredArticles.length === 0 ? (
<p className="empty-tip">No hay artículos en esta categoría</p>
) : (
<div className="article-grid">
{filteredArticles.map(article => (
<div key={article.id} className="article-card">
<div className="card-content">
<span className="card-category">{article.category}</span>
<h3>{article.title}</h3>
<p>{article.summary}</p>
<span className="card-date">{article.date}</span>
</div>
</div>
))}
</div>
)}
</main>
<footer className="footer">
<p>© 2024 EJEMPLO Blog. Powered by React.</p>
</footer>
</div>
)
}
export default App
Ejemplo
.category-bar {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.category-bar button {
padding: 6px 16px;
border: 1px solid #ddd;
border-radius: 20px;
background: #fff;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.category-bar button.active {
background: #1976d2;
color: #fff;
border-color: #1976d2;
}
.category-bar button:hover {
border-color: #1976d2;
}
.result-info {
color: #999;
font-size: 14px;
margin-bottom: 16px;
}
Resumen del capítulo
En este capítulo has aprendido los tres conceptos clave de la gestión de estado en React: useState para crear y actualizar estado, el principio de actualización inmutable (operador de propagación + map/filter), y useMemo para cachear valores calculados derivados.
Con la función de filtrado por categorías, has experimentado el patrón de datos dirigido de React: «setState → re-renderizado → nueva vista».
Otras extensiones