Skip to content
This page has been auto-translated and may contain errors.View in English

Context

Elevar el estado funciona bien cuando los componentes que comparten un valor están cerca uno del otro. Se vuelve tedioso cuando no lo están. Un valor que vive cerca de la parte superior del árbol y se necesita seis niveles más abajo tiene que pasarse a través de cada componente en el medio, y cada uno de esos componentes tiene que aceptar y redirigir una prop que nunca usa. Ese relé es prop drilling, el problema en el que cayeron los Componentes compuestos y no pudieron resolverlo con Children.map. Context es la solución integrada de React: un componente provee un valor, y cualquier componente debajo puede leerlo directamente, sin importar cuántas capas haya en medio.

Piénsalo como teletransportar los datos. Los componentes en el medio nunca los ven ni los redirigen; sus listas de props se enfocanen lo que les importa.

Crear un context y proveer un valor

Context tiene tres partes: crearlo, proveer un valor, leer el valor. La creación sucede una vez, en el nivel superior de un archivo, fuera de cualquier componente. Generalmente también lo exportarás, porque los componentes que lo leen suelen vivir en otros archivos.

jsx
import { createContext } from 'react'

export const ThemeContext = createContext()

export default function App() {
  return (
    <ThemeContext value="light">
      <Header />
      <Button />
    </ThemeContext>
  )
}

Renderizar el objeto context como un componente hace que todo lo que está dentro sea un consumidor en espera. La prop value es la carga: aquí es la cadena "light", pero puede ser cualquier valor de JavaScript, un objeto, un array, incluso una función. Lo que pongas ahí es lo que recibirán los lectores debajo. La prop debe escribirse value; ese nombre es parte de la API.

La ubicación importa. El proveedor no tiene que envolver toda tu aplicación, y normalmente no debería. Colócalo alrededor del subárbol más pequeño que cubra cada componente que necesite el valor. Si solo una esquina de la interfaz se preocupa por el tema, provee el tema en el ancestro común de esa esquina y deja el resto del árbol fuera.

Nota de versión

Antes de React 19, un context no podía renderizarse directamente; renderizabas <ThemeContext.Provider value="light"> en su lugar. React 19 hizo que el objeto context en sí sea renderizable como el proveedor. La forma .Provider aún funciona y es lo que verás en la mayoría de las bases de código existentes y en las lecciones del curso, pero React planea deprecarla en una versión futura e incluye un codemod para el cambio, así que opta por la forma básica <ThemeContext> en código nuevo.

Leer el valor con useContext

Cualquier componente bajo el proveedor lee el valor con useContext, el hook presentado por primera vez en Hooks, pasándole el objeto context para que React sepa cuál context leer. Por eso el context se exporta.

jsx
import { useContext } from 'react'
import { ThemeContext } from './App'

function Header() {
  const theme = useContext(ThemeContext)

  return (
    <header className={`${theme}-theme`}>
      <h1>{theme === 'light' ? 'Light' : 'Dark'} Theme</h1>
    </header>
  )
}

useContext(ThemeContext) devuelve lo que el ThemeContext más cercano arriba de este componente está sosteniendo actualmente: aquí, la cadena "light". No hay enhebrado de props entre App e Header, y componentes entre ellos en un árbol más grande no mencionarían el tema en absoluto. Una aplicación puede tener varios contexts a la vez, cada uno creado por separado y cada uno leído pasando su propio objeto a useContext.

Hacerlo funcionar: estado más context

Un value="light" codificado nunca cambia. El patrón que hace útil el context lo empareja con state: el estado es propietario del valor y lo actualiza, el context lo entrega. Pasa un objeto que contenga tanto el valor actual como una función que lo cambie, y cada consumidor puede leer o actualizar el tema desde cualquier lugar bajo el proveedor.

jsx
export const ThemeContext = createContext()

export default function App() {
  const [theme, setTheme] = useState('light')

  function toggleTheme() {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light')
  }

  return (
    <ThemeContext value={{ theme, toggleTheme }}>
      <Header />
      <Button />
    </ThemeContext>
  )
}

function Button() {
  const { theme, toggleTheme } = useContext(ThemeContext)

  return (
    <button className={`${theme}-theme`} onClick={toggleTheme}>
      Switch theme
    </button>
  )
}

Cuando se hace clic en el botón, toggleTheme se ejecuta, el estado en App se actualiza, App se renderiza de nuevo, y el proveedor pasa el nuevo objeto hacia abajo. Cada componente que lee el context se renderiza de nuevo con el valor actualizado. Los consumidores desestructuran solo las propiedades que necesitan.

Este truco de alcance también funciona en pequeña escala. Un componente como un menú desplegable puede renderizar un proveedor alrededor de sus propios hijos, dándole a las piezas dentro una estado compartido que nunca se filtra al resto de la página. Aquí está el menú de Componentes compuestos, terminado:

jsx
const MenuContext = createContext()

function Menu({ children }) {
  const [open, setOpen] = useState(false)
  const toggle = () => setOpen(prevOpen => !prevOpen)

  return <MenuContext value={{ open, toggle }}>{children}</MenuContext>
}

function MenuButton({ children }) {
  const { toggle } = useContext(MenuContext)

  return <button onClick={toggle}>{children}</button>
}

Menu es propietario de open y coloca tanto él como toggle en el context; MenuButton sube a buscar el toggle con useContext, y un MenuDropdown escrito de la misma manera lee open para decidir si renderizar. Nada en el medio redirige nada, así que un llamador puede envolver el botón en tantos divs de diseño como quiera y el menú sigue funcionando.

Exportar el context crudo funciona, pero la mayoría de las bases de código envuelven el patrón en dos piezas: un componente proveedor que es propietario del estado, y un hook personalizado que lee el context y falla ruidosamente cuando se usa incorrectamente.

jsx
const ThemeContext = createContext(null)

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light')

  function toggleTheme() {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light')
  }

  return (
    <ThemeContext value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext>
  )
}

export function useTheme() {
  const context = useContext(ThemeContext)
  if (context === null) {
    throw new Error('useTheme must be used within a ThemeProvider')
  }
  return context
}

Ahora los consumidores escriben const { theme } = useTheme() y nunca importan el objeto context, lo que mantiene la superficie pública del módulo a exactamente dos cosas, el proveedor y el hook. El argumento para createContext es el respaldo que React devuelve cuando ningún proveedor se ubica arriba del consumidor, así que null aquí significa "nadie proveyó esto," y eso es lo que la verificación prueba. Sin ella, el consumidor falla mientras desestructura un valor null, y el stack trace apunta al consumidor en lugar de al proveedor faltante.

Cuando el value de un proveedor cambia, React renderiza de nuevo cada componente que lee ese context, sin importar cuándo se siente. "Cambios" significa una comparación fallida de Object.is, que es donde el valor de objeto literal muerde: el objeto pasado a value en el código anterior construye un objeto nuevo en cada renderización del propietario del proveedor, así que cada renderización de ese propietario renderiza de nuevo cada consumidor, independientemente de si theme realmente cambió.

Cuando el propietario se renderiza de nuevo solo porque el valor cambió, como App lo hace aquí, memorizar no compra nada. Paga cuando el propietario se renderiza de nuevo por razones no relacionadas y arrastra cada consumidor con él. Envolver el objeto en useMemo es la mitad del arreglo: toggleTheme es una función nueva en cada renderización, así que el memo se recomputa cada vez a menos que esa función se estabilice también, con useCallback o pasando el setTheme ya estable en lugar de un wrapper.

Dos herramientas estructurales importan más que la memorización. Primero, divide contexts a lo largo de la frecuencia de actualización: un valor que cambia una vez por sesión y un valor que cambia en cada pulsación de tecla no pertenecen al mismo proveedor, porque el rápido arrastra los consumidores del lento con él.

Segundo, recuerda que context es un mecanismo de entrega en lugar de un administrador de estado. Resuelve "este valor es necesario lejos." No hace nada acerca de cómo se procesan, derivan o persisten las actualizaciones. Cuando el estado compartido de una aplicación crece más allá de un tema y un objeto de usuario, ese es el punto en el que una tienda dedicada como Redux Toolkit o Zustand gana su peso.

Cómo cada tienda llega a tus componentes

Las dos entregan sus tiendas de manera diferente: una aplicación de Redux Toolkit envuelve el árbol en <Provider store={store}> de react-redux, que entrega la tienda a los hooks debajo a través de context, mientras que la configuración predeterminada de Zustand exporta un hook a nivel de módulo que cualquier componente llama directamente, sin proveedor involucrado.

JunoContext salta las capas intermedias Cuando un valor tiene que viajar lejos en el árbol, pasarlo a través de cada componente se vuelve agotador rápidamente.

Context permite que un componente cerca de la parte superior diga "aquí está el valor" y permite que cualquier componente debajo suba y lo tome directamente con useContext. Los componentes en el medio nunca lo tocan.

Emparéjalo con estado y el valor puede cambiar también: mantén el valor y su función de actualización juntos en el proveedor, y cualquier consumidor puede leerlo o cambiarlo.

JunoContext salta las capas intermedias Crea un context a nivel de módulo, renderízalo como un proveedor con un value, y léelo debajo con useContext. Hazlo funcionar pasando estado y su actualizador juntos en un objeto.

En código real, envuelve el proveedor en su propio componente y expón un hook al estilo useTheme que lance fuera del proveedor; los consumidores obtienen una API limpia y los errores se presentan con un mensaje legible.

JunoContext salta las capas intermedias Cada consumidor se renderiza de nuevo cuando el valor provisto falla Object.is, y un literal de objeto en línea lo falla en cada renderización del proveedor, así que memoriza el valor donde la frecuencia de actualización lo importa. Divide contexts que se actualizan a diferentes velocidades, y limita proveedores al subárbol más pequeño que los necesita.

Trata context como entrega para valores lejanos; una vez que el estado compartido necesita administración real, busca una tienda, ya sea que se monte en context como Redux Toolkit o lo omita como Zustand.

Próximo: Render props y componentes sin cabeza, donde un componente provee comportamiento y te permite suministrar el marcado.