Use Context
Overview¶
use_context is a ReactPy hook that reads a value provided by a component higher in the tree, without every component in between having to pass it along.
value = use_context(SomeContext)
Context is for values a whole subtree needs and the middle of the tree does not: a theme, the current user, the active locale, a store. If a component would only forward a prop to its children, context removes both the prop and the component's reason to exist.
Reference¶
use_context(context)¶
Call it at the top level of a component, passing the context object you created with create_context.
from reactpy import use_context
Parameters¶
context: The object returned bycreate_context. It is matched by identity, so the same object must be used on both the provider and the reader — import it from one place.
Returns¶
The value of the nearest context provider above the component. With no provider anywhere above, it returns the default given to create_context.
Caveats¶
- Reading context is not a subscription that spares renders. When a provider re-renders with a new value, its children re-render — which they would have anyway, since ReactPy re-renders children unconditionally. Context hands you the new value; it does not narrow the work. See What context does not do.
- It returns the default, not
None, when nothing provides it.create_context("light")yields"light"to an unprovided reader, so a component renders on its own without a wrapper. Pick a default that makes sense standalone, orNoneplus an explicit fallback branch. - Call it during the render, not from an effect body. An effect runs later, in its own task, where no hook is active; calling
use_contextthere raisesRuntimeError: No life cycle hook is active. Are you rendering in a layout?Read it in the component and let the closure carry the value in — see Reading context inside an effect. - The context object must be hashable and must be the same object. Passing something else raises:
TypeError: <x> is not a Contextfor a class or string,TypeError: unhashable type: 'dict'if you hand it a dict or list. Twocreate_contextcalls over the same default are two unrelated contexts. - A
Nonedefault narrows the type.create_context(None)infers a value type ofNone, so a type checker rejectsvalue=[...]. Annotate the name to widen it:Cart: Context[list[str] | None] = create_context(None). - Like every hook, call it only at the top level. Unusually, context is safe to call conditionally — it reserves no hook slot, so unlike
use_stateit cannot shift other hooks' state.
Creating and providing a context¶
A context is created once, at module level, with create_context:
from reactpy import create_context
Theme = create_context("light")
What comes back is an ordinary function, and calling it is the provider: pass the children positionally and the value as a keyword.
Theme(child_a, child_b, value="dark")
There is no Theme.Provider — calling the context is the only form. The provider renders a nameless fragment around its children, so it adds nothing to the output.
The value= keyword is optional; omit it and the default from create_context is used. A key= keyword is also accepted, with the same meaning as on any other element.
Usage¶
Passing data deeply¶
swatch needs the theme. toolbar does not — it renders swatch and never mentions it. Without context, toolbar would take a theme argument purely to forward it, and every component added in between would inherit that obligation.
Press Switch to dark: the provider gets a new value, and the component far below reads it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
1 | |
Choosing a default¶
The default is what a component sees when no provider is above it, which is exactly the situation in a unit test or a standalone render. Make it something the component can handle.
cart_badge below copes with either — rendered bare it says the cart is unavailable, wrapped in the provider it counts the items.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
1 | |
Note the type annotation on Cart. Because the default is None, without the annotation a type checker infers the value type as None and rejects Cart(shop(), value=["widget", ...]).
Nesting providers¶
The provider nearest above the reader wins, and sibling subtrees are independent:
@component
def page():
return Theme(
html.p(use_context(Theme)), # "outer"
Theme(html.p(use_context(Theme)), value="inner"), # "inner"
value="outer",
)
Verified: the first reads "outer", the second "inner"; two sibling providers with different values each see their own. Removing a provider returns its descendants to the default — nothing stays pinned to the last value.
Reading context inside an effect¶
Effects run in their own task, outside any component render, so no hook is active there and use_context will not work. Read it in the component and use the local:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
1 | |
Putting use_context(Config) inside on_mount instead raises RuntimeError: No life cycle hook is active. Are you rendering in a layout? — and since an effect error is logged only on the server, it is easy to miss.
What context does not do¶
Context solves plumbing: it moves a value past components that do not care about it. It is not a performance feature.
- It does not stop children from re-rendering. ReactPy has no render bailout, so when the provider re-renders, everything below it re-renders too. A component reading a context whose value did not change still re-renders — verified: toggling a theme re-ran the bodies of both a context reader and an unrelated sibling.
- It does not make a value reactive by itself. The value changes because the component that renders the provider re-rendered with a new one. If nothing re-renders the provider, readers keep seeing the old value.
- It does not replace state. Context carries values down. To let a component change a shared value, put the state in a common ancestor and pass the setter down alongside it — or move to a reducer, see
use_reducer.
For a value that only one component's subtree uses, a prop is simpler and easier to trace. Reach for context when the value genuinely crosses several levels that have no use for it.
Troubleshooting¶
Readers always get the default¶
Usually one of:
- The provider is not above the reader in the tree.
Theme(child, value=...)must be an ancestor — calling it and discarding the result provides nothing. - The provider and the reader hold different context objects. Two
create_contextcalls are different contexts even with the same default, so create it once and import it. - The provider's
value=never changes, so readers keep seeing the value from the render that set it.
RuntimeError: No life cycle hook is active. Are you rendering in a layout?¶
You called use_context outside a render — from an effect body, from a plain function called before the component runs, or directly in a test. Move the call into the component body.
TypeError: ... is not a Context¶
You passed something other than the result of create_context — often the provider instead of the context (use_context(Theme(child, value="x"))), or the class of a value rather than a context.
My type checker rejects the provider's value¶
The default narrowed the type. Annotate the module-level name with Context[<your type> | None], as in the default-value example above.