8000 GitHub - forge-42/web-events: Framework-agnostic custom event-driven handling library for the web
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

forge-42/web-events

Repository files navigation

๐Ÿ“ฆ @forge42/web-events

A tiny web-standards based utility for event-driven web apps.

GitHub Repo stars GitHub npm npm npm GitHub top language

Leverages @standard-schema/spec to safely dispatch and listen to custom events in the browser OR the server by using the EventTarget interface.

โœจ Features

  • ๐Ÿ›ก๏ธ Zero dependencies for a lean footprint
  • โœ… Type-safe event dispatching and listening
  • ๐Ÿ”Ž Runtime validation powered by @standard-schema/spec
  • ๐Ÿงช Minimal, browser-only API
  • ๐Ÿชถ Lightweight and framework-agnostic

๐Ÿ“ฆ Installation

npm install @forge42/web-events
# or
pnpm add @forge42/web-events

๐Ÿ“– Usage

Core API

This works both on the client and server side, as it uses the EventTarget interface.

import { registerEvent } from "@forge42/web-events"
import { z } from "zod"

// Define your schema
const UserLoggedIn = z.object({
  userId: z.string(),
  timestamp: z.number(),
})

// Register event
const [dispatch, listener] = registerEvent("user:logged-in", UserLoggedIn)

// Listen for event
const unsubscribe = listener((data) => {
  console.log("User logged in:", data.userId)
})

// Dispatch event
dispatch({
  userId: "abc123",
  timestamp: Date.now(),
})

The registerEvent function returns a tuple containing the dispatch function and the listener function. The listener can be used to subscribe to the event, and it returns an unsubscribe function to stop listening.

const [dispatch, listener] = registerEvent("user:logged-in", UserLoggedIn)

Once the event is registered, you can dispatch it with the expected data structure, and it will be validated against the schema.

// This will match the schema provided to the registerEvent function
dispatch({
  userId: "abc123",
  timestamp: Date.now(),
})

If you want to listen to the incoming events in your application, you can use the listener function. It accepts a callback that will be invoked with the validated data whenever the event is dispatched.

const unsubscribe = listener((data) => {
	console.log("User logged in:", data.userId)
})

// Optionally you can unsubscribe later
unsubscribe()

You can also pass in a third optional parameter to the registerEvent function to specify the event init options, such as bubbles, cancelable, or composed. This allows you to control the behavior of the event in the DOM.

It also takes in an optional property emitter which can be used to specify the EventTarg 8000 et that will emit the event. This is useful if you want to use a custom event target instead of the default one.

const [dispatch, listener] = registerEvent("user:logged-in", UserLoggedIn, {
	bubbles: true,
	composed: true,
	emitter: document // or any other EventTarget
})

React

You can also use @forge42/web-events in a React application. It comes with a React specific API that allows you to register and listen to events in a more React-friendly way.

import { registerReactEvent } from "@forge42/web-events/react"
import { z } from "zod"
// Define your schema
const UserLoggedIn = z.object({
	userId: z.string(),
	timestamp: z.number(),
})
// Register event
const [dispatchUserLogin, useUserLoggedInEvent] = registerReactEvent("user:logged-in", UserLoggedIn)

// Use the event in a React component
export default function UserComponent() {
	const userLoggedIn = useUserLoggedInEvent()

	const handleLogin = () => dispatchUserLogin({
			userId: "abc123",
			timestamp: Date.now(),
		})

	return (
		<div>
			<button onClick={handleLogin}>Log In</button>
			{userLoggedIn && <p>User logged in: {userLoggedIn.userId}</p>}
		</div>
	)
}

The registerReactEvent function returns a tuple containing the dispatch function and a custom React hook (useEventListener) that you can use to access the event data in your components. The hook accepts an object with an onEvent callback that will be called whenever the event is received.

const [dispatch, useEventListener] = registerReactEvent("user:logged-in", UserLoggedIn)
// Use the event in a React component
export default function UserComponent() {
   const userLoggedIn = useEventListener({
   	onEvent: (data) => {
   		console.log("User logged in:", data.userId)
   	},
   })

   const handleLogin = () => {
   	dispatch({
   		userId: "abc123",
   		timestamp: Date.now(),
   	})
   }

   return (
   	<div>
   		<button onClick={handleLogin}>Log In</button>
   		{userLoggedIn && <p>User logged in: {userLoggedIn.userId}</p>}
   	</div>
   )
}

useEvent hook

The useEvent hook is a custom React hook that allows you to listen to events in a more declarative way. It accepts an initialized registerEvent instance and returns the data.

import { useEvent } from "@forge42/web-events/react"

const messageSentEvent = registerEvent("message:sent", z.object({
  message: z.string(),
  count: z.number().optional(),
  date: z.date().optional(),
}));

export default function MessageComponent() {
	const messageSent = useEvent(messageSentEvent, {
		onEvent: (data) => {
			console.log("Message sent:", data);
		},
	});

	return (
		<div>
			{messageSent ? (
				<p>Message: {messageSent.message}, Count: {messageSent.count}, Date: {messageSent.date?.toISOString()}</p>
			) : (
				<p>No message sent yet.</p>
			)}
		</div>
	);
}

Schema Validation

The library accepts any schema compatible with @standard-schema/spec, such as Zod, Yup, or Joi. This allows you to define the structure of your event data and ensures that only valid data is dispatched.

import { z } from "zod"
const UserLoggedIn = z.object({
	userId: z.string(),
	timestamp: z.number(),
})

Framework Integration

You can easily integrate @forge42/web-events with popular frameworks like React, Vue, or Svelte. Here's an example for React:

import React, { useEffect } from "react"
import { registerEvent } from "@forge42/web-events"

const UserLoggedIn = z.object({
	userId: z.string(),
	timestamp: z.number(),
})
// Do not initialize the event inside the component to avoid re-registering on every render
const [dispatch, listener] = registerEvent("user:logged-in", UserLoggedIn)

const UserComponent = () => {
	useEffect(() => {
		const unsubscribe = listener((data) => {
			console.log("User logged in:", data.userId)
		})

		return () => {
			unsubscribe() // Cleanup on unmount
		}
	}, [])

	const handleLogin = () => {
		dispatch({
			userId: "abc123",
			timestamp: Date.now(),
		})
	}

	return <button onClick={handleLogin}>Log In</button>
}

export default UserComponent

๐Ÿ›ก๏ธ Type Safety

This utility uses your schema to infer:

dispatch(input) expects data that matches the input schema

listener(callback) receives validated and parsed data

๐Ÿ—‚๏ธ Example Use Case

Use it to:

Broadcast form submissions

Track user interactions across decoupled modules

Replace fragile string-based pub/sub logic with type-safe guarantees

๐Ÿ“„ License

MIT License

0