useContext lee un valor que da un componente padre, así que un componente en lo profundo del árbol puede obtenerlo sin que cada componente intermedio lo pase como prop. Creas un contexto con createContext, envuelves una parte del árbol en un proveedor con un value y llamas a useContext(TheContext) donde necesites ese valor.
Toolbar nunca menciona el tema, y sin embargo ambos botones cambian cuando marcas la casilla. Esa es justamente la idea del contexto: los componentes intermedios no cargan una prop que no usan, que es el problema del prop drilling que resuelve el contexto.
Los tres pasos
// 1. Create it once, outside any component, usually in its own file
export const ThemeContext = createContext('light');
// 2. Provide a value to a part of the tree
<ThemeContext value={theme}>
<Page />
</ThemeContext>
// 3. Read it in any component inside that part
const theme = useContext(ThemeContext);
En React 19 el propio objeto de contexto es el proveedor. El código antiguo escribe <ThemeContext.Provider value={theme}>, que sigue funcionando, así que verás ambos.
useContext busca hacia arriba en el árbol el proveedor más cercano de ese contexto. Un componente lee el más cercano, así que puedes anidar proveedores para sobrescribir un valor en una sección.
El valor por defecto
El argumento de createContext es lo que devuelve useContext cuando no hay ningún proveedor por encima del componente. No cambia cuando cambia el estado; es un valor de respaldo.
El primer Greeting no tiene proveedor por encima y recibe el valor por defecto 'en'. El segundo lee 'es', y el tercero lee el 'ja' más cercano. Cambia el value="es" exterior por value="ja" y solo cambia la segunda línea.
Actualizar el contexto desde un hijo
El contexto solo pasa un valor hacia abajo. Para que un hijo profundo pueda cambiarlo, guarda el valor en el estado donde está el proveedor y pon el setter en el contexto junto a él.
LoginButton llama a setUser, el estado de App cambia, el proveedor recibe un valor nuevo y Header también se actualiza. Ninguno de los dos componentes recibió una prop.
Cada consumidor se vuelve a renderizar cuando cambia el valor
Cuando cambia el value de un proveedor, React renderiza cada componente que llama a useContext para él, aunque ese componente esté dentro de un memo y sus props sigan igual. React compara el valor viejo y el nuevo con Object.is. Así que value={{ user, setUser }} es un objeto nuevo en cada renderizado de App, y todos los consumidores vuelven a renderizar aunque user no haya cambiado.
Ambos lectores registran una vez en el primer renderizado. Haz clic en el botón: solo PlainReader vuelve a registrar, porque su contexto recibió un objeto nuevo. useMemo le entrega a MemoContext el mismo objeto hasta que cambia name, así que MemoReader se queda quieto. Esto solo importa cuando el renderizado se vuelve lento; en una app pequeña, un objeto nuevo por renderizado está bien.
Dividir los contextos
Un valor que cambia a menudo y un valor que nunca cambia no deberían compartir contexto. Si el setter vive junto al estado, un componente que solo necesita llamar a setUser igual se renderiza cada vez que cambia user. Ponlos en dos contextos y cada consumidor se suscribe solo a lo que lee:
const UserContext = createContext(null);
const SetUserContext = createContext(() => {});
function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<SetUserContext value={setUser}>
<UserContext value={user}>{children}</UserContext>
</SetUserContext>
);
}
El setUser de useState conserva la misma identidad para siempre, así que los componentes que solo leen SetUserContext nunca se renderizan por un inicio de sesión. La misma idea se aplica a datos no relacionados: un tema y un carrito de compras van en contextos separados, no en un gran AppContext.
Un toque final habitual es un hook personalizado como useUser() que llama a useContext(UserContext) y lanza un error claro cuando falta el proveedor, para que quienes lo usan nunca importen directamente el objeto de contexto.
Contexto, props o una librería de estado
- Las props son la opción por defecto. Pasar un valor dos o tres niveles hacia abajo es claro y fácil de seguir.
- El contexto encaja con valores que muchos componentes necesitan a muchas profundidades y que cambian poco: tema, idioma, el usuario con sesión iniciada, un objeto de feature flags.
- Una librería de estado (Redux Toolkit, Zustand, Jotai y otras) encaja con estados grandes que se actualizan a menudo, donde quieres que un componente se suscriba a una parte y se salte los renderizados causados por el resto. El contexto no tiene ese selector: un consumidor renderiza ante cualquier cambio del valor.
Antes de recurrir al contexto o a una librería, comprueba si pasar componentes como children elimina el drilling. A menudo lo hace, sin ninguna API nueva.
Preguntas frecuentes
¿Qué hace useContext en React?
Devuelve el valor actual de un contexto, tomado del proveedor más cercano por encima del componente en el árbol. Cuando cambia el valor de ese proveedor, todos los componentes que lo leen se vuelven a renderizar.
¿Para qué sirve el valor por defecto de createContext?
Es lo que devuelve useContext cuando no hay ningún proveedor por encima del componente. Es útil para pruebas y para componentes renderizados por sí solos, y nunca cambia.
¿Todavía necesito Context.Provider en React 19?
No. En React 19 puedes renderizar el propio contexto como proveedor: <ThemeContext value="dark">. <ThemeContext.Provider value="dark"> sigue funcionando, así que el código antiguo sigue ejecutándose.
¿Cómo actualizo el valor de un contexto desde un componente hijo?
Guarda el valor en el estado del componente que renderiza el proveedor, y pon el setter en el contexto junto al valor: value={{ theme, setTheme }}. El hijo lee setTheme con useContext y lo llama.
¿El contexto de React reemplaza a Redux?
El contexto pasa un valor hacia abajo por el árbol; no gestiona estado por sí solo. Combinado con useState o useReducer cubre muchas apps. Una librería de estado agrega cosas que el contexto no tiene, como suscribirse a una sola parte para que las actualizaciones no relacionadas no vuelvan a renderizar un componente, además de devtools y middleware.