Skip to content

From child to parent ​

Communication between a child component and its parent is based on custom events.

The parent registers an event listener on the child. The child emits the event, which triggers the callbacks defined in the parent.

You can implement your own event system if you prefer, but Jadis includes a built-in method useEvents that ensures memory safety and supports strong typing with both TypeScript and JSDoc, making event handling clean, scalable, and type-safe.

Signature ​

The main idea is to pass an interface to the function's generic in TypeScript and in JSDoc. In JavaScript, we need to use an object that represents the interface as closely as possible.

In JavaScript an object or an array can not be described more precisely than Object and Array.

You can read more about typing events!

javascript
useEvents({eventName: PrimitiveConstructor});
// PrimitiveConstructor can be any primitive constructor: 
// String, Number, Boolean, etc.
typescript
useEvents<{eventName: Type}>();
javascript
/** @type {import('@jadis/core').UseEventsHandler<{eventName: Type}>} */
useEvents();

Parameter ​

  • An object used only for typing in JavaScript, unused in TypeScript and JSDoc, {someEvent: PrimitiveConstructor}
javascript
const events = this.useEvents({someEvent: String});
javascript
/** @type {import('@jadis/core').UseEventsHandler<{someEvent: string}>} */
  events = this.useEvents();

Return value ​

  • An object with 2 methods: register and emit.
    • register is used to subscribe to an event
    • emit is used to emit an event.

emit dispatches a CustomEvent on the child itself. It does not bubble or cross shadow roots, so only listeners registered on that element receive it; the callback receives the payload directly. register binds the listener to the child's killSignal: register it in the parent's onConnect(), which runs again after each reconnection.

UseEvents Usage ​

jsx
/// <reference types="@jadis/core/jsx-runtime" />
/** @jsxImportSource @jadis/core */

import { Jadis, createSelector } from '@jadis/core';

class ChildComponent extends Jadis {
  static selector = createSelector('child-component');

  events = this.useEvents({ someEvent: String });

  templateHtml() {
    return <button id="btn">Click me</button>;
  }

  onConnect() {
    this.on(this.getElement('#btn'), 'click', () => {
      this.events.emit('someEvent', 'Button clicked!');
    });
  }
}

class ParentComponent extends Jadis {
  static selector = createSelector('parent-component');

  refs = this.useRefs((ref) => ({
    message: ref('#message'),
    childComponent: ref('child-component'),
  }));

  templateHtml() {
    return (
      <>
        <child-component></child-component>
        <p id="message"></p>
      </>
    );
  }

  onConnect() {
    this.refs.childComponent.events.register('someEvent', (data) => {
      this.refs.message.textContent = data;
    });
  }
}

ChildComponent.register();
ParentComponent.register();
tsx
import { Jadis } from '@jadis/core';

class ChildComponent extends Jadis {
  static readonly selector = 'child-component';

  events = this.useEvents<{
    someEvent: string;
  }>();

  templateHtml(): Node {
    return <button id="btn">Click me</button>;
  }

  onConnect(): void {
    this.on(this.getElement('#btn'), 'click', () => {
      this.events.emit('someEvent', 'Button clicked!');
    });
  }
}

class ParentComponent extends Jadis {
  static readonly selector = 'parent-component';

  refs = this.useRefs((ref) => ({
    message: ref<HTMLParagraphElement>('#message'),
    childComponent: ref<ChildComponent>('child-component'),
  }));

  templateHtml(): Node {
    return (
      <>
        <child-component></child-component>
        <p id="message"></p>
      </>
    );
  }

  onConnect(): void {
    this.refs.childComponent.events.register('someEvent', (data) => {
      this.refs.message.textContent = data;
    });
  }
}

ChildComponent.register();
ParentComponent.register();
jsx
// @ts-check
/// <reference types="@jadis/core/jsx-runtime" />
/** @jsxImportSource @jadis/core */

import { Jadis, createSelector } from '@jadis/core';

class ChildComponent extends Jadis {
  static selector = createSelector('child-component');

  /** @type {import('@jadis/core').UseEventsHandler<{someEvent: string}>} */
  events = this.useEvents();

  templateHtml() {
    return <button id="btn">Click me</button>;
  }

  onConnect() {
    this.on(this.getElement('#btn'), 'click', () => {
      this.events.emit('someEvent', 'Button clicked!');
    });
  }
}

class ParentComponent extends Jadis {
  static selector = createSelector('parent-component');

  refs = this.useRefs((ref) => ({
    /** @type {HTMLParagraphElement} */
    message: ref('#message'),
    /** @type {ChildComponent} */
    childComponent: ref('child-component'),
  }));

  templateHtml() {
    return (
      <>
        <child-component></child-component>
        <p id="message"></p>
      </>
    );
  }

  onConnect() {
    this.refs.childComponent.events.register('someEvent', (data) => {
      this.refs.message.textContent = data;
    });
  }
}

ChildComponent.register();
ParentComponent.register();

Typing the Event System ​

The best way to type the event system is with TypeScript or JSDoc. With either, you can pass an interface that clearly describes what each event’s payload should be.

Note ​

If you don’t want to send a payload, you can define the event as undefined, like so:

typescript
{
  someEvent: undefined;
}

Even when using plain JavaScript, you can still provide partial typing by passing an object that defines each event name as a key and its type as the corresponding value.

Just use primitive constructors like:

  • Number
  • String
  • Boolean
  • BigInt
  • Symbol
  • Function
  • Array
  • Object
javascript
{
  someEvent: String,
  someOther: Object,
}