RMD056 — One context provided twice by the same component
const [ThemeProvider] = createContext({ color: "slate" }, { label: "Theme" });
class Panel extends Component {
// ✗ two Providers of one context, on one component — this throws
base = this.use(ThemeProvider, () => ({ color: "slate" }));
accent = this.use(ThemeProvider, () => ({ color: "amber" }));
render() {
return <Card />;
}
}
A component publishes a context on one object, so the second Provider would replace the first under
the same key: every descendant reads "amber", and base is unreachable from below.
What hides it is that base still works here. A Provider reads as well as provides, so
this.base.color is "slate" inside Panel while every component under it sees "amber" — the
component that made the mistake is the one place the mistake is invisible. That is why this
throws in every build, like a write to props
(RMD004) and a plain-object props bag
(RMD055): a development-only report would leave a
shipped page handing the wrong value to whichever descendant asked.
Write two scopes instead. A component that renders this.props.children scopes its context to what is
inside it, which is what a <Provider> element does in a framework that has fragments:
const [ThemeProvider, ThemeConsumer] = createContext({ color: "slate" }, { label: "Theme" });
class Scope extends Component<{ color: string; children?: RamondaNode }> {
theme = this.use(ThemeProvider, () => ({ color: this.props.color }));
render() {
return this.props.children;
}
}
class Panel extends Component {
render() {
return (
<div>
<Scope color="slate">
<Card />
</Scope>
<Scope color="amber">
<Card />
</Scope>
</div>
);
}
}
Two independent scopes, and a consumer inside each finds its own with nothing passed down. That
works because a context object is created from the component that renders a node — so a child handed
in as children inherits the wrapper's context, not the context of whoever wrote the JSX.
Nesting is untouched and needs no scope wrapper. A Provider on a descendant component shadows the one above it for its own branch, which is ordinary and is never refused: the check asks whether this component already published the key itself, and a Provider above it never makes that true.
single is a different question. It declares whether nesting is a fault — two on one path, on
different components — and a context that welcomes nesting is still broken by two on one component. So
this takes no option: there is no version of it an author would choose.
Splitting the keys between two Providers is not a way out, and the types already close it. A
Provider takes its options whole, so the second cannot supply half — it would replace the channel and
the first half would fall back to the default. If the two values are for different purposes, they are
two contexts: call createContext twice.
Next
- All diagnostics — every code the framework can report.
- Checking your app — the faults proved from the source, before anything runs.