Guides

Writing a Component Library

Rules for a component library that applications use; props tracked by a single binding, the developer's data used as it is, nothing tracked inside the component, parts drawn by hand, writing only on change, a temporary attribute on the developer's component, the component an event came from, lifetime traps and measuring.

A component library (controls, a data grid, a scheduler) is used in applications it does not know, and often with thousands of its parts on a single page. Application code can lean on reactivity; library code should not. The rules on this page were worked out by writing a complete kit and measuring it. Read the Creating Components and bindings pages first.

Three ways in

A library component listens to only three things:

  1. Its props: each one a value or a function (Bind<T>).
  2. The developer's data: used as it is. The same objects are kept and handed back; they are not wrapped with reactive(), copied or transformed. A change made in place is read again with reState() or an explicit refresh().
  3. The developer's reState() call.

Everything else is the component's own state, and the component draws it by hand.

Props: a value or a function, tracked by a single binding

const handed = new WeakMap<object, { value: unknown }>();
export function handOver<V>(control: { value: V }, value: V) {
  const last = handed.get(control);
  if (last && Object.is(last.value, value)) return;
  handed.set(control, { value });
  control.value = value;
}

Nothing is tracked inside

export function onLanguage(component: ComponentBase, draw: () => void) {
  untracked(draw);
  const changed = uiLocale.onChanged(() => !component.isDisposed && untracked(draw));
  component.motif.setDisposable(() => changed.dispose());
}

The component's tree

Writing only on change

A temporary attribute on the developer's component

const held = part.attr.hold({ disabled: true, tabindex: -1, "aria-disabled": "true" });
this.motif.register(held);

The component an event came from

this.motif.on("click:capture", (_sender, e) => {
  if (part.motif.isEventFrom(e)) return;
  e.preventDefault();
  e.stopImmediatePropagation();
});

Lifetime

Long work

Checking the work

Next step

To publish the library, go on to the Publishing an npm Library for MotifJS page.