El manejo de estado dentro de React es uno de los conceptos que nos ayudará a manejar la data dentro de nuestros componentes y vistas. Muy probablemente lo primero que se te venga a la mente para tener control sobre este sea el hook useState
.
No hay ningún problema con useState
si hablamos del estado local de tus componentes, pero el usarlo para el estado global de tu aplicación te dará muchos dolores de cabeza. Es por eso que te vengo a presentar a su versión con superpoderes: useReducer.
¿Qué tiene de especial useReducer? 🤔
Este grandioso hook nos provee un dispatcher para poder actualizar el estado de formas que nosotros establecemos previamente. Y, a su vez, también nos permite acceder al estado que contiene.
🧾 Aunque al escuchar el nombre de “reducer” se nos venga a la mente el nombre de Redux, esta no es una alternativa a la herramienta, simplemente React se inspiró en Redux para crear este hook
El problema de useState es el que nosotros creamos lógica extra en nuestro componente por cada acción que deba actualizar el estado, mientras que useReducer se lleva toda esa lógica a un archivo aparte y solo proporciona esos métodos a cada componente.
Visualmente se parece mucho a useState pero requiere una configuración adicional (crear un reducer) para que puedas sacarle todo el provecho
Su sintaxis es la siguiente:
const [state, dispatch] = useReducer(reducer, initialState)
- state: se encarga de proporcionar el estado que mantiene
- dispatch: es el método que va a leer la instrucción que buscas realizar y pasarla al reducer
- reducer: es una función que recibe el estado inicial y una acción realizará una operación u otra en el estado. Siempre de manera inmutable, no podemos modificar el estado, si no crear una copia a partir del anterior.
- initialState: es el estado inicial en el cual vas a definir tu punto de entrada
Por ejemplo, vamos a hacer el típico componente de un contador que suma o resta una unidad.
Con useState haríamos algo así 👇🏻
// counter.jsx
import React, { useState } from 'react'
const Counter = () => {
const [count, setCount] = useState(0)
const increment = () => {
setCount(count + 1)
}
const decrement = () => {
setCount(count - 1)
}
return(
<div>
<button type="button" onClick={increment}> + </button>
<button type="button" onClick={decrement}> - </button>
<p>The current count is: {count}</p>
</div>
)
}
useReducer por su parte, nos permite tener algo más limpio como esto
// Counter.jsx
import React, { useReducer } from 'react'
const initialState = {count: 0}
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return {count: state.count + 1};
case 'DECREMENT':
return {count: state.count - 1};
default:
throw new Error("The option doesn't exist");
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return(
<div>
<button
type="button"
onClick={() => dispatch({ type: "INCREMENT" })}>
+
</button>
<button
type="button"
onClick={() => dispatch({ type: "DECREMENT" })}>
-
</button>
<p>The current count is: {state.count}</p>
</div>
)
}
💻 Demo funcionando en este codepen
De esa forma los componentes solo se encargan de pintarse en pantalla y la lógica llega preparada desde otro lugar de nuestra App.
Sin embargo, seguimos teniendo el detalle que no estamos manejando un estado global, simplemente separamos las funciones en lugares diferentes.
¿Cómo es que esto escala mejor que useState? Hasta pareciera que useReducer hace un código mucho más difícil de mantener. 😰
Sácale el mejor provecho con context ⚡
Como habrás visto en el ejemplo anterior, useReducer no es tan eficaz para manejar estados locales, lo recomendable es combinarlo con React Context para crear un estado global.
Vamos a crear una App que pueda manipular la información de los productos de un e-commerce mientras te explico como realizar la configuración de context con useReducer.
1️⃣ Crea un archivo GlobalState.js
Este archivo contendrá el estado global junto con el reducer que nos ayudará a actualizarlo, lo deberás poner en tu carpeta src (source) para mayor comodidad.
Su estructura puede verse un tanto compleja pero aquí te explico para que sirve cada cosa.
// importamos React junto a useReducer y createContext
import React, { useReducer, createContext } from 'react'
// Recuerda que para acceder a nuestro contexto desde multiples lugares de
// nuestra app, deberemos exportarlo
export const ProductsContext = createContext()
// Aquí vamos a crear el estado inicial que tendrá el context y consumiremos con
// useReducer
const initialState = {
products: [
{
id: Date.now().toString(),,
img: "https://picsum.photos/100",
name: "Shoes",
description: "Shoes for people",
price: 25,
quantity: 2
}
]
}
// Este será nuestro reducer que nos ayudará a crear o eliminar los productos
// dependiendo el action.type que reciba
const reducer = (state, action) => {
switch(action.type){
case "ADD_PRODUCT":
return{
products: [...state.products, action.payload]
}
case: "REMOVE_PRODUCT":
return{
products: state.products.filter((product) => product.id !== action.payload),
}
default:
return {
state
}
}
}
// De igual forma, exportaremos el provider que envolvera todos los componentes
// de nuestra app
export const ProductsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
// Dentro del value del provider colocaremos el state y el dispatch para
// manejar el estado global
return (
<ProductsContext.Provider value={[state, dispatch]}>
{children}
</ProductsContext.Provider>
);
};
3️⃣ Envuelve tus componentes con el Provider
Este paso es súper importante para que puedas consumir el contexto correctamente desde cualquier lugar de la App (recuerda que también debes importarlo en cada componente que lo necesite)
Vamos a usar 3 componentes dentro del provider los cuales detallaremos más adelante:
// index.js
import React from 'react'
// Las rutas son relativas a la organización de tus componentes
import { CreateProduct } from "./components/CreateProduct"
import { DeleteProduct } from "./components/DeleteProduct"
import { ListItems } from "./components/ListItems"
// importamos el context provider para poder consumirlo
import { ProductsContextProvider } from './GlobalState.js'
// Envolvemos a los componentes que van a tener acceso a este estado
// En este caso será toda nuestra aplicación
const App = () => {
return(
<ProductsContextProvider>
<CreateProduct />
<DeleteProduct />
<ListItems />
</ProductsContextProvider>
)
}
4️⃣ Crea tus componentes que manipularán la data
Como habrás visto, nuestro reducer solo contempla la creación, eliminación y lectura de la data por lo que vamos a crear un componente por cada uno de ellos
Solo faltaría crear el método update para que sea un CRUD, pero eso se te queda de tarea. 😉
CreateProduct.jsx (Create):
// CreateProduct.jsx
// Vamos a importar React como de costumbre y también el hook useContext que nos
// permitira acceder a los valores del context provider, pero este solo
// funciona si le pasamos nuestro contexto que creamos al incio
import React, { useContext } from "react"
import { ProductsContext } from "../GlobalState.js"
export const CreateProduct = () => {
const [state, dispatch] = React.useContext(ProductsContext)
// El evento onSubmit del form nos permitirá mandar la data mediante el dispatch
// este debe ser un objeto con dos propiedades:
// type - El cual será uno de los método del reducer
// payload - la data de nuestro producto que queremos crear
return(
<form onSubmit={(e) => {
e.preventDefault()
dispatch({
type: "ADD_PRODUCT",
payload: {
id: Date.now().toString(),,
img: e.target.img.value,
name: e.target.name.value,
description: e.target.description.value,
price: Number(e.target.price.value),
quantity: Number(e.target.quantity.value),
}
})
}}>
<input required placeholder="img" type="text" name="img" />
<input required placeholder="name" type="text" name="name" />
<input required placeholder="description" type="text" name="description" />
<input required placeholder="price" type="number" name="price" />
<input required placeholder="quantity" type="number" name="quantity" />
<button type="submit">Create item</button>
</form>
)
}
ListItems.jsx (Read):
// ListItems.jsx
import React, { useContext } from "react"
import { ProductsContext } from "../GlobalState.js"
// Repetimos lo mismo que en el componente anterior
// Aquí solo vamos a consumir el estado, por lo que no es necesario incluir el dispatch
export const ListItems = () => {
const [state] = useContext(ProductsContext)
return(
<ul>
{
state.products.map(product =><li key={product.id} >{product.name}</li> )
}
</ul>
)
}
DeleteProduct.jsx (Delete):
// DeleteProduct.jsx
import React, { useContext } from "react"
import { ProductsContext } from "../GlobalState.js"
export const DeleteProduct = () => {
const [state, dispatch] = useContext(ProductsContext)
// En este componente solo vamos a presentar el estado mediante un select en el
// cual podrás eliminar los items según el id que tenga
// El dispatch en este caso solo va a recibir el id mediante el payload y
// su type será "REMOVE_PRODUCT"
return(
<form onSubmit={(e) => {
e.preventDefault()
dispatch({
type: "REMOVE_PRODUCT",
payload: e.target.id.value
})
}}>
<select name="id">
<option value="" selected> Select an option </option>
{state.products.map(product =><option value={product.id}>{product.id} {product.name}</option>)}
</select>
<button type="submit"> Delete </button>
</form>
)
}
5️⃣ Pule los detalles de esta lista de productos
Ya tienes toda la lógica necesaria para que este proyecto funcione, visualmente no se ve tan bien así que deja volar tu imaginación y dale ese toque. Aquí abajo te dejo una muestra de como me quedó este mini-proyecto estilizado

En este codepen encontrarás la demo
Con un solo hook y el context API logramos crear una app que fácilmente te puede ayudar a mantener una lista de productos en un e-commerce, las entradas de un blog o crear una ToDo App (como la que vemos con el profe JuanDC en esta clase).
Ahora imagínate el potencial que puedes sacarle a React si conoces todas sus herramientas, las cuales aprenderás sin dificultad en los siguientes cursos 👇🏻
Curso de React.js: Manejo Profesional del Estado