renderable Render

The resolver behind every primitive, exported so you can build your own in the same convention.

API

Signature

tsx
renderableRender(renderable: Renderable, children?: ReactNode): ReactNode;

Examples

Usage

Every primitive in the library ends up here. Exposing it means a component of your own can accept a Renderable slot and behave exactly like the built-ins, instead of inventing a third convention for the same problem.

tsx
const Panel = ({ title, children }: { title: Renderable; children: ReactNode }) => (
  <section>
    <header>{renderableRender(title)}</header>
    {children}
  </section>
);

Details

The three branches

A function is a component and gets called through createElement. An element with children to inject is cloned, its own children replaced. Anything else — an element with no children to inject, a string, a number, null — is returned untouched.

tsx
renderableRender(Panel);                  // <Panel />
renderableRender(<Panel tone="warn" />);  // the element, untouched
renderableRender(Panel, children);        // <Panel>{children}</Panel>
renderableRender(<Panel />, children);    // cloned, its own children replaced
renderableRender("just text");            // returned as is

The test is typeof === "function", nothing more: a component is always a function and a ReactNode never is, so the two forms are told apart with certainty rather than with a heuristic. Note that the argument is children, plural in name only: it is a single node, and passing it is what distinguishes a wrapper from a plain slot.

Building your own primitive

Two rules keep a custom primitive consistent with the built-ins. Make a prop a Renderable when it is rendered conditionally, so the component form can defer its evaluation; keep it a plain ReactNode when it always passes through, since there is nothing to defer and a second form would only add a way to get it wrong.

And forward children only when your component genuinely wraps: calling renderableRender(slot, children) on a slot that is meant to stand alone will silently replace whatever that element already contained.

Forwarding children

Pass children as the second argument only for wrappers: the component form receives them as its children, and the element form has its own children replaced by them.