Context
import { createContext } from "@ramonda/core";
const [ThemeProvider, ThemeConsumer] = createContext({
theme: "light",
accent: "pink",
});
createContext returns a pair: a provider hook and a consumer hook. Both are hooks, so
neither adds an element.
class App extends Component {
@state theme = "light";
provider = this.use(ThemeProvider, (self: App) => ({ theme: self.theme, accent: "pink" }));
}
class Badge extends Component {
ctx = this.use(ThemeConsumer);
render() {
return <span className={this.ctx.theme}>{this.ctx.accent}</span>;
}
}
a component two levels down:light / pink
Nothing between App and Badge knows the theme exists.
Reads are per key
this.ctx.theme subscribes to theme, not to the whole context. A component reading only
theme is not re-rendered when accent changes.
That is not an optimisation you opt into — it is how the context is built. Each key is its own signal.
The default is a real fallback
The object passed to createContext is used for any key a provider does not supply — not just
when there is no provider at all.
createContext({ theme: "light", accent: "pink" });
// a provider passing only { theme } leaves accent as "pink"
An explicitly provided undefined still wins. The default is for keys nobody mentioned.
No provider above
Reading a consumer with no provider anywhere above it falls back to the default and reports
RMD003 in development, naming the context and the key.
It is reported on the read, not on construction, because a hook may legitimately hold a consumer it never reads.
Labelling it for devtools
createContext({ theme: "light" }, { label: "Theme" });
The devtools tree then shows ThemeProvider / ThemeConsumer rather than anonymous nodes.
Development only.
When not to use it
Context is for values a whole subtree needs and nothing in between should have to carry — theme, locale, the current user, a router. For a value one child needs, pass a prop. Threading a prop through two levels is clearer than a context nobody can see.
Next
- Children — passing markup down.