Lydiob7

Blog
Programación
ReactJS
React Context
React Hooks
JavaScript
TypeScript
7 jun 2025, 16:28
React Context
Entre las variadas formas de manejar el estado en una aplicación de React tenemos la opción del Context para lo cual no necesitamos instalar ninguna librería externa. Acá te enseño cómo crear un Context y cómo consumirlo en nuestros componentes
Tomas Scattini (Lydiob7)
Desarrollador Full Stack

Introducción

React con sus componentes de JSX son una hermosa abstracción de funciones normales de JavaScript que se encargan de modificar el DOM y de mostrar el estado más actualizado de nuestra aplicación en el HTML. Las "props" que podemos pasarles no son más que argumentos pasados a las funciones subyacentes.

Pero un problema que surge de esta arquitectura es que si queremos compartir el estado de nuestra aplicación entre un componente que se encuentra en un nivel externo y uno que se encuentra más abajo en el árbol tenemos que pasar ese estado como props a cada uno de los nodos intermedios.

Los "contextos" de React vienen a ayudarnos a resolver este problema (prop drilling) con una API simple de comprender y utilizar. En este artículo vamos a ver con un ejemplo real cómo podemos ponerlo en práctica. Para lxs que no tengan ganas de leer todo el artículo tenemos también la versión en video.

El código que vamos a estar usando esta disponible en este repo de GitHub en la rama "main" con el estado inicial. En la rama "final" van a encontrar el código completo.

Video

createContext()

La función createContext() nos permite create un contexto de React con el cual podemos proveer a nuestros componentes con el estado global de nuestra aplicación.

El ejemplo que vamos a utilizar es una aplicación tipo chat que maneja los usuarios y sus conversaciones entre sí. Vamos a partir de un componente App que contiene toda la lógica necesaria y la pasa a los componentes internos via props.

function App() {
    const [rooms, setRooms] = useState<Room[]>([]);
    const [users, setUsers] = useState<User[]>([]);
    const [authUser, setAuthUser] = useState<string | null>(null);
    const [selectedRoom, setSelectedRoom] = useState<Room | null>(null);

    const login = useCallback(async (userId: string) => {
    // ... 
    }, []);

    const logout = () => {
    // ...
    };

    const getUsers = useCallback(async () => {
      // ...
    }, [users.length]);

    useLayoutEffect(() => {
        getUsers();
    }, [getUsers]);

    const handleCreateNewConversation = async (userId?: string | null) => {
      // ...
    };

    const handleSendMessage = async (roomId: string, body: string) => {
      // ...
    };

    const handleReactToMessage = async (messageId: string, reaction: string) => {
      // ...
    };

    return (
        <div>
          ...  
          <UserRoomsList
              authUser={authUser}
              handleCreateNewConversation={handleCreateNewConversation}
              rooms={rooms}
              selectedRoom={selectedRoom}
              setSelectedRoom={setSelectedRoom}
              users={users}
          />
      
          <ChatWindow
              authUser={authUser}
              handleReactToMessage={handleReactToMessage}
              handleSendMessage={handleSendMessage}
              rooms={rooms}
              selectedRoom={selectedRoom}
              setSelectedRoom={setSelectedRoom}
          />
          ...
        </div>
    );
}

export default App;

Vamos a crear primero el contexto y definimos las propiedades que va a tener

import { createContext } from "react";

export interface ChatContextProps {
    authUser: string | null;
    handleCreateNewConversation: (userId?: string | null) => void;
    handleReactToMessage: (messageId: string, reaction: string) => void;
    handleSendMessage: (roomId: string, body: string) => void;
    login: (userId: string) => void;
    logout: () => void;
    rooms: Room[];
    selectedRoom: Room | null;
    setSelectedRoom: Dispatch<SetStateAction<Room | null>>;
    users: User[];
}

export const ChatContext = createContext<ChatContextProps | null>(null);

Context.Provider

Después con ese nuevo contexto vamos a crear el provider con el que tenemos que envolver nuestra app y le pasamos los valores del estado que queremos propagar:

<ChatContext.Provider {...props} value={{
    authUser,
    handleCreateNewConversation,
    handleReactToMessage,
    handleSendMessage,
    login,
    logout,
    rooms,
    selectedRoom,
    setSelectedRoom,
    users
}}>
    ...
</ChatContext.Provider>

De esta manera, desde cualquier componente que se encuentre envuelto con este proveedor vamos a poder consumir cualquiera de las variables dentro del "value".

Podemos crear un componente nuevo que englobe todo lo que hicimos para el nuevo context y esté separado de nuestro componente App:

const ChatContextProvider = ({ children, ...props }: { children: ReactNode }) => {
   // nuestro estado y funciones para mutarlo

    const value = {
       ...
    }

    return (
        <ChatContext.Provider {...props} value={value}>
            {children}
        </ChatContext.Provider>
    );
};

export default ChatContextProvider;

useProvider()

Para consumir el estado de nuestro context tenemos dos posibilidades con las que nos provee React. La primera y más popular es el hook useProvider. A este hook se le pasa el contexto creado por createContext y nos devuelve el estado actual del "value". Hay que tener en cuenta de que si se llama a este hook por fuera del context provider o se intenta acceder al context antes de que esté definido (en nuestro caso puede ser null en el primer render) entonces el context va a ser undefined. Por esto tenemos que chequear si el contexto existe antes de intentar acceder a sus valores.

import { useContext } from "react";
import { ChatContext } from "./ChatContextProvider";

const ChatMessage = () => {
    const context = useContext(ChatContext);

    if (!context) throw new Error("useChatContext should be used inside a ChatContextProvider");

   // Acá podemos acceder a context.rooms y todos los demás valores
}

Como hicimos con el provider también podemos abstraer el código de este hook para no tener que importar el contexto cada vez y chequear si existen los valores.

import { useContext } from "react";
import { ChatContext } from "./ChatContextProvider";

export const useChatContext = () => {
    const context = useContext(ChatContext);

    if (!context) throw new Error("useChatContext should be used inside a ChatContextProvider");

    return context;
};

Context.Consumer (Bonus track)

Como segunda opción para consumir el estado del contexto, React nos provee con el Context.Consumer que es un componente con el que podemos rodear los componentes que queremos que accedan al estado.

Este nuevo componente "Consumer" requiere que pasemos una función como "children" que va a recibir el estado como propiedad.

Otra vez vamos a abstraer en un nuevo archivo este código para hacerlo más reutilizable.

import { ConsumerProps } from "react";
import { ChatContext, ChatContextProps } from "./ChatContextProvider";

const ChatContextConsumer = ({ children, ...props }: ConsumerProps<ChatContextProps>) => {
    return (
        <ChatContext.Consumer {...props}>
            {(context) => (context ? children(context) : "Consumer must be used inside a Provider")}
        </ChatContext.Consumer>
    );
};

export default ChatContextConsumer;

Conclusiones

Muchas veces no hace falta crear soluciones super complejas ni utilizar librerías de terceros para manejar el estado global. La API de contexto en React nos permite tener una estructura más limpia y organizada de nuestro estado con poco esfuerzo.

Espero que este artículo sirva para cualquiera queriendo aprender a usar el context en React. Si tienen comentarios que puedan aportar al tema son más que bienvenidos en la sección de comentarios (por favor con respeto). Este es un lugar de aprendizaje común para todxs. Gracias por leer

Iniciá sesión para comentar el post
;

Lydiob7

Español
2025 Lydiob7. Todos los derechos reservados