Pensando en React
Cada capítulo hasta ahora te enseñó una pieza: componentes, props, estado, eventos, listas. Este capítulo es donde todo se une en un método. Dado un diseño, ¿cómo decides qué son los componentes, cuáles valores son estado y dónde debe vivir ese estado? Hay una forma repetible de trabajar a través de esto, y una vez que lo hayas hecho unas cuantas veces, recurrirás a los mismos cinco pasos en casi todas las pantallas que construyas.
Vamos a recorrer una pequeña UI de principio a fin: una lista de productos buscable. Muestra un conjunto de productos, una caja de búsqueda para filtrarlos por nombre, y una casilla para ocultar lo que no está en stock. Aquí están los datos que renderiza:
const PRODUCTS = [
{ id: 1, name: 'Manzana', price: '$1', stocked: true },
{ id: 2, name: 'Fruta del dragón', price: '$1', stocked: false },
{ id: 3, name: 'Maracuyá', price: '$2', stocked: true },
]Paso 1: Divide el diseño en un árbol de componentes
Mira el diseño y dibuja recuadros alrededor de las piezas. Un buen recuadro es algo que hace un trabajo, el mismo instinto que usas cuando decides qué debe hacer una función. Nuestra pequeña pantalla se divide limpiamente en algunos:
FilterableProductListenvuelve todo.SearchBarcontiene el input de texto y la casilla de productos en stock.ProductTablemuestra la lista de productos.ProductRowes una sola fila en esa tabla.
Estos se anidan uno dentro del otro, así que forman un árbol de componentes:
FilterableProductList
├── SearchBar
└── ProductTable
└── ProductRow (uno por producto)No hay una única división correcta, y la regla de oro del capítulo componentes aplica aquí: mantén cada uno pequeño y dale un trabajo claro. Una fila sabe cómo dibujar un producto; la tabla sabe cómo distribuir filas; la barra de búsqueda sabe de los controles. Si una pieza empieza a hacer dos cosas no relacionadas, eso es usualmente una señal de dividirla.
Paso 2: Construye una versión estática solo con props
Ahora construye una versión que renderiza los datos pero no hace nada más. Sin clicks, sin filtrado, sin escribir que cambie algo. Los datos fluyen de una sola forma, hacia abajo del árbol, a través de props. No hay useState en ningún lado en este paso.
function FilterableProductList({ products }) {
return (
<div>
<SearchBar />
<ProductTable products={products} />
</div>
)
}
function ProductTable({ products }) {
return (
<table>
<tbody>
{products.map(product => (
<ProductRow key={product.id} product={product} />
))}
</tbody>
</table>
)
}
function ProductRow({ product }) {
return (
<tr>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
)
}
function SearchBar() {
return (
<form>
<input type="text" placeholder="Buscar..." />
<label>
<input type="checkbox" /> Solo mostrar productos en stock
</label>
</form>
)
}Esto renderiza la lista completa cada vez. La caja de búsqueda y la casilla están en pantalla, pero no hacen nada aún. Ese es el punto del paso: obtienes todo el layout funcionando solo con props, y confirmas que el árbol del paso 1 realmente se mantiene unido, antes de que ninguna de las partes móviles esté involucrada. El array products viene como una prop en la parte superior y se pasa hacia abajo; nada aquí recuerda o cambia nada.
Paso 3: Encuentra el estado mínimo
Ahora hazlo interactivo, y la primera pregunta es cuáles valores necesitan ser estado en absoluto. El estado es costoso de mantener correcto, así que quieres lo menos posible. El resto lo calcularás.
La prueba para cada valor es corta. Pregúntate dos cosas:
- ¿Cambia con el tiempo?
- ¿Es algo que no puedas calcular de otro valor que ya tengas?
Si ambas son sí, es estado. Si alguna es no, no lo es. Pasa los valores en nuestra UI a través de esa prueba:
- La lista de productos. Se pasa y no cambia mientras el usuario interactúa con la página, así que no es estado. Es una prop.
- El texto de búsqueda que escribió el usuario. Cambia con el tiempo, y no hay nada de lo cual calcularlo. Es estado.
- Si la casilla de productos en stock está marcada. Misma historia: cambia, y nada lo deriva. Es estado.
- La lista filtrada realmente mostrada en pantalla. Cambia, pero puedes calcularla a partir de los productos, el texto de búsqueda y la casilla. Así que no es estado.
Ese último es la regla que vale la pena grabar: deriva, no dupliques. La lista visible es los productos pasados a través de los filtros actuales, así que la calculas durante el renderizado en lugar de almacenar una segunda copia en estado:
const visible = products.filter(product => {
const matchesText = product.name
.toLowerCase()
.includes(filterText.toLowerCase())
const matchesStock = !inStockOnly || product.stocked
return matchesText && matchesStock
})Si hubieras almacenado visible en su propio useState, tendrías que acordarte de actualizarlo cada vez que los productos, el texto o la casilla cambien. Pierdes uno y la pantalla muestra una lista obsoleta. Derivarla significa que hay una sola fuente de verdad, y nunca puede desincronizarse, porque se recalcula fresco en cada renderizado. Así que el estado mínimo aquí es exactamente dos valores: filterText e inStockOnly.
Paso 4: Decide dónde debe vivir cada pieza de estado
Tienes dos piezas de estado. Ahora averigua qué componente es dueño de cada una. La regla es poner el estado en el padre común más cercano de cada componente que lo necesita, que es la misma idea que el capítulo elevando el estado explora en profundidad.
Averigua quién necesita cada valor:
SearchBarnecesita ambos valores, porque renderiza el input y la casilla que los muestran.ProductTabletambién necesita ambos valores, porque filtra las filas usándolos.
SearchBar y ProductTable son hermanos. Un valor solo puede fluir hacia abajo a través de props, así que ningún hermano puede pasar estado al otro. El componente más cercano que está arriba de ambos es FilterableProductList. Ese es dónde va el estado:
import { useState } from 'react'
function FilterableProductList({ products }) {
const [filterText, setFilterText] = useState('')
const [inStockOnly, setInStockOnly] = useState(false)
return (
<div>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
onFilterTextChange={setFilterText}
onInStockOnlyChange={setInStockOnly}
/>
<ProductTable
products={products}
filterText={filterText}
inStockOnly={inStockOnly}
/>
</div>
)
}El estado vive en el padre, y los valores fluyen hacia abajo a ambos hijos como props. ProductTable los lee para construir su lista visible; SearchBar los lee para llenar el input y la casilla.
Paso 5: Agrega la interacción que actualiza el estado
Los valores llegan a los hijos, pero el usuario aún no puede cambiarlos. Los datos solo fluyen hacia abajo, así que para enviar un cambio hacia arriba pasas los setters hacia abajo como props y dejas que el hijo los llame. FilterableProductList ya pasa onFilterTextChange e onInStockOnlyChange arriba. SearchBar los conecta a los inputs:
function SearchBar({
filterText,
inStockOnly,
onFilterTextChange,
onInStockOnlyChange,
}) {
return (
<form>
<input
type="text"
placeholder="Buscar..."
value={filterText}
onChange={e => onFilterTextChange(e.target.value)}
/>
<label>
<input
type="checkbox"
checked={inStockOnly}
onChange={e => onInStockOnlyChange(e.target.checked)}
/>{' '}
Solo mostrar productos en stock
</label>
</form>
)
}Ahora el ciclo está cerrado. Escribir en la caja llama onFilterTextChange, que es setFilterText arriba en el padre. Eso actualiza el estado, el padre se re-renderiza, el nuevo filterText fluye hacia atrás a ambos hijos, ProductTable recalcula su lista visible, y la pantalla coincide con lo que escribió el usuario. La casilla funciona de la misma forma. El estado baja como un valor, los cambios suben como una llamada a función, y la lista derivada la sigue por su cuenta.
Ese es todo el método: dibuja el árbol de componentes, construyelo estático con props, encuentra el estado mínimo, elévalo al dueño correcto, luego conecta la interacción. Casi cualquier pantalla que encuentres cede a esos cinco pasos.
Lo siguiente: React Accesible, donde la interfaz que acabas de diseñar se vuelve usable para todos.

