Context
Context¶
Provide and consume values across the component tree without prop drilling.
from wybthon import component, dynamic, h, p
from wybthon.context import create_context, Provider, use_context
Theme = create_context("light")
@component
def Label():
return p("Theme: ", dynamic(lambda: use_context(Theme)))
view = h(Provider, {"context": Theme, "value": "dark"}, h(Label, {}))
Reactive value¶
Provider's value prop is signal-backed: passing a getter (or a
signal accessor) makes the provided value reactive. Consumers that
read use_context inside a reactive hole will update automatically
when the value changes, without a subtree re-mount.
from wybthon import component, create_signal, dynamic, h, p, use_context, Provider
@component
def App():
theme, set_theme = create_signal("dark")
return h(Provider, {"context": Theme, "value": theme}, # ← getter
h(Label, {}))
@component
def Label():
# Wrap in dynamic so the text node updates when ``theme`` flips.
return p(dynamic(lambda: f"Theme: {use_context(Theme)}"))
If you only need a static value, pass it as-is; the Provider handles both shapes uniformly.
How it works: the ownership tree¶
Context values are stored directly on the reactive ownership tree,
not on a separate render-time stack. When a Provider component
mounts, the reconciler creates a per-context signal on that component's
_ComponentContext owner. Consumers read the signal via
use_context, so they participate in normal reactive tracking.
Root Owner
└── ComponentContext (App)
└── ComponentContext (Provider) ← _context_map = {Theme.id: signal("dark")}
└── ComponentContext (Label)
└── render effect
← use_context(Theme) walks up, reads the signal,
and tracks it for future updates.
This ownership-based lookup means context is available at any point during a component's lifecycle (setup phase, render function, or inside effects) as long as the code runs under an owner that is a descendant of the provider.
Provider scoping¶
Each Provider sets its value on its own component context. Nested
providers for the same context naturally shadow outer ones because the
ownership-tree walk finds the nearest ancestor first:
h(Provider, {"context": Theme, "value": "light"},
h(Provider, {"context": Theme, "value": "dark"},
h(Label, {})), # sees "dark"
h(Label, {}), # sees "light"
)
Performance¶
Context lookup is a simple parent-pointer walk: no dict copies, no
stack manipulation. The cost is proportional to the depth between the
consumer and the nearest provider, which is typically small. When the
Provider value is a getter, an internal effect mirrors it into the
context signal so consumers update reactively without the Provider
itself re-rendering.
Next steps¶
- See the
contextAPI forContext,Provider, anduse_context. - Read Lifecycle and Ownership for how the ownership tree works.
- Explore Stores when you need a richer reactive container.