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