useContext
useContext
es un Hook de React que te permite leer y suscribirte a un contexto desde tu componente.
const value = useContext(SomeContext)
Referencias
useContext(SomeContext)
Llama useContext
en el nivel superior de tu componente para leer y suscribirte al contexto.
import { useContext } from 'react';
function MyComponent() {
const theme = useContext(ThemeContext);
// ...
Parámetros
SomeContext
: El contexto que creaste previamente concreateContext
. El propio contexto no guarda información, solo representa el tipo de información que puedes proporcionar o leer desde tus componentes.
Salidas
useContext
devuelve el valor del contexto para el componente que lo llama. Está determinado como el value
pasado al SomeContext.Provider
más cercano arriba del componente que llama en el árbol. Si no existe tal proveedor, entonces el valor devuelto será el defaultValue
que le pasaste a createContext
para ese contexto. El valor devuelto siempre está actualizado. React rerenderiza automáticamente los componentes que leen algún contexto si este cambia.
Advertencias
- La llamada de
useContext()
en un componente no es afectada por los proveedores devueltos desde el mismo componente. El<Context.Provider>
correspondiente necesita estar arriba del componente que hace la llamada deuseContext()
. - React rerenderiza automáticamente todos los hijos que usen un contexto particular empezando desde el proveedor que recibe un
value
diferente. Los valores anteriores y los siguientes son comparados conObject.is
. Saltarse el rerenderizado conmemo
no evita que los hijos reciban valores de contexto frescos de arriba. - Si tu sistema de compilación produce módulos duplicados en la salida (lo cual puede pasar si usas enlaces simbólicos), esto puede romper el contexto. Pasar algo a través del contexto solo funciona si
SomeContext
que usas para proporcionar el contexto ySomeContext
que usas para leerlo son exactamente el mismo objeto, como está determinado por la comparación===
.
Uso
Pasar datos de manera profunda en el árbol
Llama useContext
en el nivel superior de tu componente para leer y suscribirte al contexto.
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
// ...
useContext
devuelve el valor del contexto para el contexto que le pasaste. Para determinar el valor del contexto, React busca en el árbol de componentes y encuentra el proveedor de contexto más cercano arriba para ese contexto en particular.
Para pasar el contexto a un Button
, envuélvelo o envuelve a uno de sus componentes padres dentro del proveedor de contexto correspondiente:
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... renderiza botones dentro ...
}
No importa cuántas capas de componentes hay entre el proveedor y el Button
. Cuando un Button
en cualquier lugar dentro de Form
llama useContext(ThemeContext)
, recibirá "dark"
como valor.
import { createContext, useContext } from 'react'; const ThemeContext = createContext(null); export default function MyApp() { return ( <ThemeContext.Provider value="dark"> <Form /> </ThemeContext.Provider> ) } function Form() { return ( <Panel title="Welcome"> <Button>Registrarse</Button> <Button>Iniciar sesión</Button> </Panel> ); } function Panel({ title, children }) { const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( <section className={className}> <h1>{title}</h1> {children} </section> ) } function Button({ children }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className}> {children} </button> ); }
Actualizar los datos pasados a través del contexto
A menudo, querrás que el contexto cambie a través del tiempo. Para actualizar el contexto, necesitas combinarlo con el estado. Declara una variable de estado en el componente padre, y pasa el estado actual como el valor de contexto al proveedor.
function MyPage() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Form />
<Button onClick={() => {
setTheme('light');
}}>
Cambiar a tema claro
</Button>
</ThemeContext.Provider>
);
}
Ahora cualquier Button
dentro del proveedor recibirá el valor actual de theme
. Si llamas setTheme
para actualizar el valor de theme
que pasaste al proveedor, todos los componentes Button
se rerenderizarán con el nuevo valor 'light'
.
Ejemplo 1 de 5: Actualizar un valor a través del contexto
En este ejemplo, el componente MyApp
guarda una variable de estado la cual es luego pasada al proveedor de ThemeContext
. Marcar la casilla “Dark mode” actualiza el estado. Cambiar el valor proporcionado rerenderiza todos los componentes que utilizan ese contexto.
import { createContext, useContext, useState } from 'react'; const ThemeContext = createContext(null); export default function MyApp() { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={theme}> <Form /> <label> <input type="checkbox" checked={theme === 'dark'} onChange={(e) => { setTheme(e.target.checked ? 'dark' : 'light') }} /> Usar modo oscuro </label> </ThemeContext.Provider> ) } function Form({ children }) { return ( <Panel title="Bienvenido"> <Button>Registrarse</Button> <Button>Iniciar sesión</Button> </Panel> ); } function Panel({ title, children }) { const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( <section className={className}> <h1>{title}</h1> {children} </section> ) } function Button({ children }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className}> {children} </button> ); }
Fíjate que value="dark"
pasa el string "dark"
, pero value={theme}
pasa el valor de la variable JavaScript theme
con llaves de JSX. Las llaves también te permiten pasar valores de contexto que no son strings.
Especificar un valor por defecto
Si React no puede encontrar ningún proveedor de ese contexto en particular en el árbol padre, el valor del contexto devuelto por useContext()
será igual al valor por defecto que especificaste cuando creaste ese contexto:
const ThemeContext = createContext(null);
El valor por defecto nunca cambia. Si quieres actualizar el contexto, úsalo en conjunto con el estado como está descrito arriba.
A menudo, en lugar de null
, hay algunos valores significativos más que puedes usar por defecto, por ejemplo:
const ThemeContext = createContext('light');
De esta manera, si accidentalmente renderizas algún componente sin su proveedor correspondiente, no se romperá. Esto también ayuda a que tus componentes funcionen bien en un ambiente de pruebas sin configurar un montón de proveedores en las pruebas.
En este ejemplo a continuación, el botón “Cambiar tema” siempre es claro, porque está afuera de cualquier proveedor de contexto del tema y el valor por defecto del contexto del tema es 'light'
. Intenta editar el tema por defecto para que sea 'dark'
.
import { createContext, useContext, useState } from 'react'; const ThemeContext = createContext('light'); export default function MyApp() { const [theme, setTheme] = useState('light'); return ( <> <ThemeContext.Provider value={theme}> <Form /> </ThemeContext.Provider> <Button onClick={() => { setTheme(theme === 'dark' ? 'light' : 'dark'); }}> Cambiar tema </Button> </> ) } function Form({ children }) { return ( <Panel title="Bienvenido"> <Button>Registrarse</Button> <Button>Iniciar sesión</Button> </Panel> ); } function Panel({ title, children }) { const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( <section className={className}> <h1>{title}</h1> {children} </section> ) } function Button({ children, onClick }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className} onClick={onClick}> {children} </button> ); }
Sobreescribir el contexto para una parte del árbol
Puedes sobreescribir el contexto para una parte del árbol al envolver esa parte en un proveedor con un valor diferente.
<ThemeContext.Provider value="dark">
...
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
...
</ThemeContext.Provider>
Puedes anidar y sobreescribir proveedores tantas veces como necesites.
Ejemplo 1 de 2: Sobreescribir un tema
Aquí, el botón dentro del Footer
recibe un valor del contexto diferente ("light"
) que los objetos fuera ("dark"
).
import { createContext, useContext } from 'react'; const ThemeContext = createContext(null); export default function MyApp() { return ( <ThemeContext.Provider value="dark"> <Form /> </ThemeContext.Provider> ) } function Form() { return ( <Panel title="Bienvenido"> <Button>Registrarse</Button> <Button>Iniciar sesión</Button> <ThemeContext.Provider value="light"> <Footer /> </ThemeContext.Provider> </Panel> ); } function Footer() { return ( <footer> <Button>Ajustes</Button> </footer> ); } function Panel({ title, children }) { const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( <section className={className}> {title && <h1>{title}</h1>} {children} </section> ) } function Button({ children }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className}> {children} </button> ); }
Optimizar rerenderizados al pasar objetos y funciones
Puedes pasar cualquier valor a través del contexto, incluyendo objetos y funciones.
function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
function login(response) {
storeCredentials(response.credentials);
setCurrentUser(response.user);
}
return (
<AuthContext.Provider value={{ currentUser, login }}>
<Page />
</AuthContext.Provider>
);
}
Aquí, el valor del contexto es un objeto de JavaScript con dos propiedades, una de las cuales es una función. Siempre que MyApp
se rerenderice (por ejemplo, en una actualización de ruta), este será un objeto diferente apuntando a una función diferente, así que React también tendrá que rerenderizar todos los componentes en lo profundo del árbol que llamen useContext(AuthContext)
.
En aplicaciones más pequeñas, esto no es un problema. Sin embargo, no hay necesidad de rerenderizarlas si los datos subyacentes, como currentUser
, no han cambiado. Para ayudar a React a aprovechar esa información, puedes envolver la función login
con useCallback
y envolver la creación del objeto en un useMemo
. Esta es una optimización del rendimiento:
import { useCallback, useMemo } from 'react';
function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
const login = useCallback((response) => {
storeCredentials(response.credentials);
setCurrentUser(response.user);
}, []);
const contextValue = useMemo(() => ({
currentUser,
login
}), [currentUser, login]);
return (
<AuthContext.Provider value={contextValue}>
<Page />
</AuthContext.Provider>
);
}
Como resultado de este cambio, incluso si MyApp
necesita rerenderizarse, los componentes que llaman useContext(AuthContext)
no se rerenderizarán a menos que currentUser
haya cambiado. Lee más sobre useMemo
y useCallback
.
Solución de problemas
Mi componente no ve el valor desde mi proveedor
Hay algunas maneras comunes en que esto puede ocurrir:
- Estás renderizando
<SomeContext.Provider>
en el mismo componente (o debajo de) donde estás llamandouseContext()
. Mueve<SomeContext.Provider>
arriba y afuera del componente que llamauseContext()
. - Puede que hayas olvidado envolver tu componente con
<SomeContext.Provider>
, o quizás lo colocaste en una parte diferente del árbol de la que pensabas. Revisa si la jerarquía está correcta utilizando React DevTools. - Puede que tengas un problema de compilación con tus herramientas que provoque que
SomeContext
como es visto desde el componente proveedor y queSomeContext
como es visto desde el componente que lee sean dos objetos diferentes. Esto puede suceder si usas enlaces simbólicos, por ejemplo. Puedes verificar esto al asignarlos a variables globales comowindow.SomeContext1
ywindow.SomeContext2
y luego verificar siwindow.SomeContext1 === window.SomeContext2
en la consola. Si no son el mismo, necesitas arreglar ese problema a nivel de herramienta de compilación.
Siempre recibo undefined
de mi contexto a pesar de que el valor por defecto es diferente
Puede que tengas un proveedor sin un value
en el árbol:
// 🚩 No funciona: No hay prop value
<ThemeContext.Provider>
<Button />
</ThemeContext.Provider>
Si te olvidas de especificar un value
, es como pasar value={undefined}
.
Es posible que hayas utilizado un nombre de prop diferente por error:
// 🚩 No funciona: la prop debería llamarse "value"
<ThemeContext.Provider theme={theme}>
<Button />
</ThemeContext.Provider>
En ambos casos deberías ver una advertencia de React en la consola. Para solucionarlos llama a la prop value
:
// ✅ Pasando la prop value
<ThemeContext.Provider value={theme}>
<Button />
</ThemeContext.Provider>
Fíjate que el valor por defecto de tu llamada createContext(defaultValue)
solo es usado si no hay ningún proveedor que coincida arriba en absoluto. Si hay algún componente <SomeContext.Provider value={undefined}>
en algún lugar del árbol, el componente llamando useContext(SomeContext)
recibirá undefined
como el valor del contexto.