Context
Make a value available to everything a component renders, scoped to that component.
Context lets a component make a value available to everything it renders, without passing it through every component in between.
Info: A provided value is scoped to the component that provides it. Siblings, parents and everything rendered outside it never see it.
When to Use Context
Context or a Module-Level Signal
Signals do not need a component to own them, so a signal declared at module level is already shared state that any component can import:
1let session = Signal.make(None)
For a value the whole app shares on the client, that is the simplest option. Reach for context when a global stops being enough:
- Per-instance state. Two
Tabson a page each need their own selected tab, shared with their ownTabchildren. A module-level signal can only hold one. - Children you do not write. A component that receives
childrenfrom its caller cannot close over a local variable for them. Context reaches them without new props at every call site. - Server rendering. Module-level state lives as long as the server process, so one request can see another request's values unless every global is reset. A provided value only exists for the render that provided it.
- Tests. Each test can mount its own provider instead of mutating and resetting a global.
Using Context
Declaring a Context
Declare a context once, with the value a component gets when nothing above it provides one:
1let theme = Context.make("light")
Providing a Value
Wrap a subtree in Context.Provider:
1<Context.Provider context=theme value="dark">2 <Toolbar />3</Context.Provider>
A component can also provide a value for everything it renders by calling Context.provide in its own body. Every instance of the component then gets its own value:
1let selection = Context.make(Signal.make(0))23module Tabs = {4 @xote.component5 let make = (~children: View.node) => {6 Context.provide(selection, Signal.make(0))7 <div class="tabs"> {children} </div>8 }9}
Context.provide raises when it is called outside a component body, because there is no render to scope the value to.
Reading a Value
Read the nearest provided value with Context.use:
1module Tab = {2 @xote.component3 let make = (~index: int, ~label: string) => {4 let selected = Context.use(selection)5 <button6 class={Signal.get(selected) == index ? "tab active" : "tab"}7 onClick={_ => Signal.set(selected, index)}>8 {label}9 </button>10 }11}
With no provider above, use returns the default passed to Context.make.
Reactive Values
Providing a value does not make it reactive: use returns whatever was provided. To let a subtree follow a value that changes, provide a signal, as Tabs does above, and read it with Signal.get where it is used.
Scope Rules
Where use Works
Call Context.use in a component body. The value is also correct everywhere the body's work continues later:
- effects the body set up with
Effect.run, on every run; - the reactive blocks it renders, such as
View.tracked,View.ShowandView.For, including rows added long after the first render.
Event handlers, timers and promise callbacks run outside any render, so use returns the default there. Read the value in the body and close over it:
1let currentTheme = Context.use(theme)2<button onClick={_ => Console.log(currentTheme)}> {"Log theme"} </button>
Server Rendering
Server rendering and hydration see the same provided values as the client render, and nothing provided during one render is left behind for the next.
Next Steps
- Learn how components render in the View overview.
- See how providers fit into server-side rendering.