Skip to content

Repository files navigation

use-custom-event

Bundle Size npm version types

Typed custom event emitters with runtime validation via Standard Schema. Framework-agnostic core with optional React bindings. Works with any compatible validation library — no adapter needed.

Compatible Validation Libraries

Library Minimum Version
Zod 3.24.0
Valibot 1.0.0
ArkType 2.0.0
Effect Schema 3.13.0
Yup 1.7.0
Joi 18.0.0
Typia 9.2.0
VineJS 4.0.0

Any library that implements the Standard Schema spec works out of the box.

Installation

pnpm add use-custom-event

Entry Points

Import Path Description Requires React
use-custom-event Core event emitter (DOM CustomEvent) No
use-custom-event/react Core + useEventListener hook Yes
use-custom-event/broadcast BroadcastChannel event emitter No
use-custom-event/broadcast/react Broadcast + useEventListener hook Yes

Usage with Zod

Core (no React dependency)

import { z } from "zod";
import { createEventEmitter } from "use-custom-event";

const { emit, subscribe } = createEventEmitter("my-event", z.object({ name: z.string() }));

const unsubscribe = subscribe((data) => {
  console.log(data.name); // strictly typed
});
unsubscribe();

With React

import { z } from "zod";
import { createEventEmitter } from "use-custom-event/react";

const { emit, subscribe, useEventListener } = createEventEmitter(
  "my-event",
  z.object({ name: z.string() }),
);

function App() {
  useEventListener(
    useCallback((data) => {
      console.log(data.name);
    }, []),
  );

  return <button onClick={() => emit({ name: "hello" })}>Trigger</button>;
}

Usage with Valibot

import * as v from "valibot";
import { createEventEmitter } from "use-custom-event";

const { emit, subscribe } = createEventEmitter("my-event", v.object({ name: v.string() }));

Usage with ArkType

import { type } from "arktype";
import { createEventEmitter } from "use-custom-event";

const { emit, subscribe } = createEventEmitter("my-event", type({ name: "string" }));

Usage with Effect Schema

import { Schema } from "effect";
import { createEventEmitter } from "use-custom-event";

const { emit, subscribe } = createEventEmitter("my-event", Schema.Struct({ name: Schema.String }));

Usage with Yup

import * as yup from "yup";
import { createEventEmitter } from "use-custom-event";

const { emit, subscribe } = createEventEmitter(
  "my-event",
  yup.object({ name: yup.string().required() }),
);

Broadcast Channel

Broadcast Channel enables communication between tabs/windows of the same origin. Works with any compatible validation library.

Core (no React dependency)

import { z } from "zod";
import { createBroadcastChannelEventEmitter } from "use-custom-event/broadcast";

const channel = new BroadcastChannel("my-channel");
const { emit, subscribe } = createBroadcastChannelEventEmitter(
  channel,
  z.object({ name: z.string() }),
);

const unsubscribe = subscribe((data) => {
  console.log(data.name);
});
unsubscribe();

With React

import { z } from "zod";
import { createBroadcastChannelEventEmitter } from "use-custom-event/broadcast/react";

const channel = new BroadcastChannel("my-channel");
const { emit, useEventListener } = createBroadcastChannelEventEmitter(
  channel,
  z.object({ name: z.string() }),
);

function App() {
  useEventListener(
    useCallback((data) => {
      console.log(data.name);
    }, []),
  );

  return <button onClick={() => emit({ name: "hello" })}>Trigger</button>;
}

Migrating from v3

v4 decouples React bindings from the core modules. The root entry point (use-custom-event) and broadcast entry point (use-custom-event/broadcast) no longer depend on React or export useEventListener. Import from the /react subpath instead:

- import { createEventEmitter } from "use-custom-event";
+ import { createEventEmitter } from "use-custom-event/react";
- import { createBroadcastChannelEventEmitter } from "use-custom-event/broadcast";
+ import { createBroadcastChannelEventEmitter } from "use-custom-event/broadcast/react";

If you only use emit and subscribe (no hooks), your imports stay the same — no changes needed.

Migrating from v2

v3 replaces the zod peer dependency with Standard Schema support. Your existing zod schemas work unchanged if you upgrade zod to 3.24.0+. You can also switch to any other compatible library.

- import { z } from "zod"; // zod < 3.24
+ import { z } from "zod"; // zod >= 3.24 (no code changes needed)

Or switch validation libraries entirely:

- import { z } from "zod";
+ import * as v from "valibot";

  const emitter = createEventEmitter(
    "my-event",
-   z.object({ name: z.string() }),
+   v.object({ name: v.string() }),
  );

License

MIT

About

Typed custom event emitters with runtime validation via Standard Schema. Framework-agnostic core with optional React bindings. Works with any compatible validation library — no adapter needed.

Topics

Resources

Stars

7 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages