Ramonda

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