Skip to content

Dynamic

A tag or component chosen at runtime.

Dynamic renders whatever its component prop names: a tag such as "h2", or a component class. When the choice changes, it removes the old one and builds the new one. Every other prop and the children go to what it renders.

Reach for it when the thing to render is data: a heading level from a setting, a view picked by a tab, a component looked up from a field type. For a fixed set of conditions, Switch and Match or Show read better.

Tags

Pass a tag name in component. Props become attributes and the children go inside the element. Wrap the choice in a function and it follows a signal.

Components

A component class works the same way. The example keeps the current view in a signal and gives Dynamic an accessor that reads it. Each view is a class with its own state.

Click the counter a few times, switch to About and come back: the count starts again at 0, because the old view was disposed and a new one built. Clicking Counter while it is already shown does nothing.

To store a class in a signal, assign .value = Counter. signal.set(Counter) would call the class as an updater function. If you want set, write set(() => Counter).

What component accepts

You passWhat happens
component="h2"That tag, fixed.
component=${Counter}That component class, fixed. A class from another copy of Signy counts too, it is recognized by its render method.
component=${() => view.value}Called to get the choice. It runs again when a signal it read changes, and returns a tag, a class, a function component, null or undefined.
null, undefinedNothing is rendered.
Any other functionCalled as an accessor, like the arrow function above.
Anything else, such as a node or a numberA warning in development, and nothing is rendered.

This is the rule Show and For use: a function means "call me", and the call is tracked. Nothing else tells a fixed choice from a live one in an html template.

Props and children

Everything except component and children is passed on. On a tag, a function prop is a binding, like anywhere in an html template. On a component, a prop that changes must be a function too, and the component calls it, as described in props and children.

Here the select swaps the tag, the text field feeds the child, and a function prop keeps the title current. The children are made once and moved into each new tag.

Props of a component class are read when it is built. If component stays the same, a changed static prop does not rebuild it, so pass a function for anything that changes.

When it swaps

  • The effect tracks only component and the accessor it calls. What the rendered element or component reads does not rebuild it.
  • It rebuilds only when the choice is a different value (Object.is). An accessor that returns the same class again keeps the instance, with its state.
  • On a swap the old branch is disposed first: its effects stop, its onCleanup callbacks and onUnmount hooks run, and its nodes leave the page. Then the new one is built in the same place.
  • Children come from the parent, so they are not disposed with the branch. They are moved into the new element, which keeps their listeners.

Function components

A function that is not a component class is called as an accessor, so a function component must be returned from one. A bare component=${Plain} would be called with no arguments.

function Note(props) {
  return html`<em>${props.children}</em>`;
}

html`<${Dynamic} component=${() => Note}>Careful</${Dynamic}>`;

In development, Dynamic warns when the choice is neither a tag name nor a function, for example when an accessor returns a node. It then renders nothing. A function component that reads props and was passed bare throws a TypeError instead, because it is called without any.

In JSX

The same props, as a component. A prop that the compiler turns into a getter stays live on a tag. On a component class, pass a function for anything that changes.

<Dynamic component={() => view.value} user={user} />

<Dynamic component="h2" title={label.value}>
  Heading
</Dynamic>

Limits

  • component and children are reserved. A target cannot receive props with those names.
  • Event props (onClick) and ref are passed once. They are not bindings.
  • A swap rebuilds everything inside, including state in the old component. Keep state that must survive in the parent or in a store.
  • Transitions on enter and leave are not part of Dynamic.