React ofrece varias herramientas para manejar el estado de las aplicaciones, algunas más simples, otras más complejas, con lo cual tenemos esa gama de opciones que de acuerdo al tamaño de nuestra aplicación, haremos su selección. Entre ellas destacan useContext y useReducer. Estas dos funciones trabajan excepcionalmente bien juntas, especialmente en escenarios donde se necesita compartir estados entre múltiples componentes y manejar estados complejos.
1. ¿Qué es useContext?
Definición
useContext es un hook que permite acceder al valor de un contexto creado con React.createContext desde cualquier componente del árbol de React. Esto elimina la necesidad de pasar datos a través de múltiples niveles de componentes (prop drilling).
¿Cuándo usar useContext?
- Cuando varios componentes necesitan acceder al mismo dato.
- Para evitar pasar props manualmente entre componentes que no los usan directamente.
- En aplicaciones con un estado global compartido, como una lista de favoritos o un carrito de compras.
Cómo funciona
Son 3 pasos, primero, se crea un contexto:
import { createContext } from "react";
const MyContext = createContext();
export default MyContext;
Luego, un componente proveedor envuelve al árbol de componentes y suministra el valor del contexto, es decir, definimos que vamos a compartir en el estado global:
import MyContext from "./MyContext";
export const MyProvider = ({ children }) => {
const sharedState = { user: "Pedro Pérez" };
return (
<MyContext.Provider value={sharedState}>
{children}
</MyContext.Provider>
);
};
Finalmente, se utiliza useContext para consumir cualquier propiedad o función que hemos compartido anteriormente:
import { useContext } from "react";
import MyContext from "./MyContext";
const MyComponent = () => {
const context = useContext(MyContext);
return <div>{`Hello, ${context.user}`}</div>;
};
2. ¿Qué es useReducer?
Definición
useReducer es un hook que se utiliza para manejar estados más complejos en React. Es una alternativa a useState y sigue un enfoque similar al de Redux, utilizando un patrón de reducer.
¿Cuándo usar useReducer?
- Cuando el estado tiene múltiples subvalores o está estructurado en objetos complejos.
- Cuando las transiciones de estado están determinadas por tipos de acciones.
- En aplicaciones con lógica de actualización de estado centralizada y predecible.
Cómo funciona
Un reducer es una función pura que toma el estado actual y una acción, y devuelve un nuevo estado:
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("Unknown action");
}
};
useReducer se utiliza junto con el reducer y un estado inicial:
import { useReducer } from "react";
const initialState = { count: 0 };
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
</div>
);
};
Interesante destacar, que la anatomia del useReducer, tiene cierta similitud con useState, salvo que hacemos uso del reducer al iniciar el hook.
3. Combinando useContext y useReducer
Por qué combinarlos
- useContext facilita compartir el estado global entre componentes.
- useReducer organiza y simplifica la lógica de actualización del estado.
Juntos, crean una arquitectura sólida para manejar estados compartidos en aplicaciones React.
Implementación
Primero, definimos el contexto y el reducer:
import { createContext, useReducer } from "react";
const AppContext = createContext();
const initialState = { favorites: [] };
const reducer = (state, action) => {
switch (action.type) {
case "ADD_FAVORITE":
return { ...state, favorites: [...state.favorites, action.payload] };
case "REMOVE_FAVORITE":
return {
...state,
favorites: state.favorites.filter((id) => id !== action.payload),
};
default:
return state;
}
};
export const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
export default AppContext;
Luego, consumimos el contexto en los componentes:
import { useContext } from "react";
import AppContext from "./AppContext";
const FavoriteButton = ({ productId }) => {
const { state, dispatch } = useContext(AppContext);
const isFavorite = state.favorites.includes(productId);
return (
<button
onClick={() =>
dispatch({
type: isFavorite ? "REMOVE_FAVORITE" : "ADD_FAVORITE",
payload: productId,
})
}
>
{isFavorite ? "❤️" : "🤍"}
</button>
);
};
4. Ejemplo práctico: Gestión de favoritos en ecommerce
Imaginemos una aplicación donde el usuario puede navegar por productos y marcar sus favoritos.
Estado Global
Definimos el estado inicial y las acciones posibles:
const initialState = { products: [], favorites: [] };
El reducer maneja las actualizaciones:
const reducer = (state, action) => {
switch (action.type) {
case "SET_PRODUCTS":
return { ...state, products: action.payload };
case "ADD_FAVORITE":
return { ...state, favorites: [...state.favorites, action.payload] };
case "REMOVE_FAVORITE":
return {
...state,
favorites: state.favorites.filter((id) => id !== action.payload),
};
default:
throw new Error("Unknown action type");
}
};
Componentes
- Vista principal: Consume productos desde una API.
- Botón de favoritos: Permite al usuario marcar o desmarcar productos como favoritos.
Ejemplo del componente Home:
import React, { useEffect } from "react";
import { useContext } from "react";
import AppContext from "./AppContext";
const Home = () => {
const { state, dispatch } = useContext(AppContext);
useEffect(() => {
fetch("https://fakestoreapi.com/products")
.then((res) => res.json())
.then((data) => dispatch({ type: "SET_PRODUCTS", payload: data }));
}, [dispatch]);
return (
<div>
{state.products.map((product) => (
<div key={product.id}>
<h2>{product.title}</h2>
<FavoriteButton productId={product.id} />
</div>
))}
</div>
);
};
export default Home;
Componente FavoriteButton:
import React from "react";
import { useContext } from "react";
import AppContext from "./AppContext";
const FavoriteButton = ({ productId }) => {
const { state, dispatch } = useContext(AppContext);
const isFavorite = state.favorites.includes(productId);
return (
<button
onClick={() =>
dispatch({
type: isFavorite ? "REMOVE_FAVORITE" : "ADD_FAVORITE",
payload: productId,
})
}
>
{isFavorite ? "❤️" : "🤍"}
</button>
);
};
export default FavoriteButton;
El ejemplo anterior lo puedes ver desarrollado en el siguiente video:
Conclusión
Combinar useContext y useReducer permite manejar estados globales de manera eficiente y organizada. Este enfoque es ideal para aplicaciones complejas, ya que proporciona un flujo claro de datos y acciones, reduciendo el riesgo de errores y mejorando la mantenibilidad del código.
Si te gustó el contenido compartelo con tu red.
Para ver otros contenidos te invito a acceder a mi canal en YouTube:
Top comments (0)