Skip to content

ryfylke-react-as/typesafe-custom-events

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Typesafe CustomEvents

Lets you easily set up typesafe CustomEvent channels in Typescript.

Install

💾 ~0.67KB minified, no additional dependencies.

npm i typesafe-custom-events

You can also alternatively copy/paste the source directly from here.

Use

You can create typesafe custom events by setting up a new channel.

Creating a new channel

import { CustomEventChannel } from "typesafe-custom-events";

type Toast = {
  title: string;
  status: "info" | "error";
};

const toastChannel = new CustomEventChannel<Toast>();

By passing a generic, you enforce a certain type for the custom event messages.

Listening for new events

// Start listening for events
const unsubscribe = toastChannel.subscribe((event) => {
  // Do something when channel receives event
});

// Stop listening for events
unsubscribe();

Sending events

toastChannel.send({
  title: "Foobar",
  status: "info",
});

Reference

You can create a new CustomEventChannel using the following arguments:

const name: string = "custom-channel-name"; // optional

const options: {
  target?: EventTarget; // default: `globalThis`
} = {}; // optional

const channel = new CustomEventChannel(name, options);

A CustomEventChannel instance contains the following properties:

  • send (event: T) => void Send event to channel
  • subscribe (onEvent: ((onEvent) => void)) => UnsubscribeFunction Subscribes to the given channel
  • name string The channel name used to send and receive CustomEvents.
  • id string A unique identifier for the channel
  • subscriberCount number The current number of subscribers to the channel

Source

The following is the entire library source, if you prefer - you can copy/paste this into a file in your project.

const PREFIX = "tsce";

let i = 0;
const generateId = () => {
  i++;
  return `${i}`;
};

type UnsubscribeFunction = () => void;
type Options = {
  target?: EventTarget;
};

export class CustomEventChannel<T> {
  constructor(name?: string, opts?: Options) {
    this.id = generateId();
    this.name = name ?? `${PREFIX}-${this.id}`;
    this.target = opts?.target ?? globalThis;
  }
  /** Target for emitting CustomEvent */
  target: EventTarget;
  /** Name of the CustomEvent and channel */
  name: string;
  /** Unique identifier for channel */
  id: string;
  /** Total amount of subscribers */
  subscriberCount: number = 0;
  /** Sends a new event to channel subscribers */
  send(args: T) {
    if (args === undefined) return;
    this.target.dispatchEvent(
      new CustomEvent(this.name, { detail: args })
    );
  }
  /** Subscribes to events from channel */
  subscribe(onEvent: (event: T) => any): UnsubscribeFunction {
    const listener = (e: Event) => {
      const event = e as Event & {
        detail?: T;
      };
      if (event.detail !== undefined) {
        onEvent(event.detail);
      }
    };
    this.target.addEventListener(this.name, listener);
    this.subscriberCount++;
    return () => {
      this.target.removeEventListener(this.name, listener);
      this.subscriberCount--;
    };
  }
}