DEV Community

Leonardo Castillo Lacruz
Leonardo Castillo Lacruz

Posted on

Conociendo a useContext y useReduce

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;
Enter fullscreen mode Exit fullscreen mode

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>  
  );  
};

Enter fullscreen mode Exit fullscreen mode

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>;  
};

Enter fullscreen mode Exit fullscreen mode

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");  
  }  
};

Enter fullscreen mode Exit fullscreen mode

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>  
  );  
};

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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>  
  );  
};

Enter fullscreen mode Exit fullscreen mode

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: [] };

Enter fullscreen mode Exit fullscreen mode

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");  
  }  
};

Enter fullscreen mode Exit fullscreen mode

Componentes

  1. Vista principal: Consume productos desde una API.
  2. 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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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:

https://www.youtube.com/leonardocastillo79

Top comments (0)