Created
September 22, 2025 16:53
-
-
Save amit08255/400900d47c73dba4a7d835df0fb6a1ef to your computer and use it in GitHub Desktop.
React App Event Bus Demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { useEffect, useRef, useState } from "react"; | |
| // --- 1. Event Bus Implementation --- | |
| type EventMap = { | |
| "modal:open": { modalId: string }; | |
| "modal:close": { modalId: string }; | |
| "listing:delete": { listingId: string }; | |
| "notification:show": { type: "success" | "error"; message: string }; | |
| "dom:action": { | |
| action: string; | |
| element: HTMLElement; | |
| data: Record<string, any>; | |
| }; | |
| }; | |
| interface AppEvents { | |
| emit<E extends keyof EventMap>(event: E, data: EventMap[E]): Promise<void>; | |
| on<E extends keyof EventMap>( | |
| event: E, | |
| listener: (data: EventMap[E]) => void | |
| ): string; | |
| off<E extends keyof EventMap>(event: E, listenerId: string): void; | |
| } | |
| class SimpleAppEvents implements AppEvents { | |
| private listeners: { | |
| [K in keyof EventMap]?: { [id: string]: (data: EventMap[K]) => void }; | |
| } = {}; | |
| async emit<E extends keyof EventMap>(event: E, data: EventMap[E]) { | |
| const eventListeners = this.listeners[event]; | |
| if (eventListeners) { | |
| Object.values(eventListeners).forEach((listener) => listener(data)); | |
| } | |
| } | |
| on<E extends keyof EventMap>( | |
| event: E, | |
| listener: (data: EventMap[E]) => void | |
| ): string { | |
| const id = Math.random().toString(36).substr(2, 9); | |
| if (!this.listeners[event]) this.listeners[event] = {}; | |
| this.listeners[event]![id] = listener; | |
| return id; | |
| } | |
| off<E extends keyof EventMap>(event: E, listenerId: string) { | |
| if (this.listeners[event]) { | |
| delete this.listeners[event]![listenerId]; | |
| } | |
| } | |
| } | |
| const appEvents = new SimpleAppEvents(); | |
| // --- 2. Modal Handler --- | |
| const modalHandler = { | |
| openModal: (modalId: string) => { | |
| appEvents.emit("notification:show", { | |
| type: "success", | |
| message: `Modal "${modalId}" opened!`, | |
| }); | |
| }, | |
| closeModal: (modalId: string) => { | |
| appEvents.emit("notification:show", { | |
| type: "success", | |
| message: `Modal "${modalId}" closed!`, | |
| }); | |
| }, | |
| }; | |
| // --- 3. Listing Handler --- | |
| const listingHandler = { | |
| deleteListing: (listingId: string) => { | |
| // Simulate API call | |
| setTimeout(() => { | |
| appEvents.emit("notification:show", { | |
| type: "success", | |
| message: `Listing ${listingId} deleted!`, | |
| }); | |
| }, 500); | |
| }, | |
| }; | |
| // --- 4. Action Dispatcher --- | |
| const actionHandlers = new Map<string, (element: HTMLElement, data: any) => void>(); | |
| actionHandlers.set("open-modal", (element, data) => { | |
| if (data.modalId) appEvents.emit("modal:open", { modalId: data.modalId }); | |
| }); | |
| actionHandlers.set("close-modal", (element, data) => { | |
| if (data.modalId) appEvents.emit("modal:close", { modalId: data.modalId }); | |
| }); | |
| actionHandlers.set("delete-listing", (element, data) => { | |
| if (data.listingId) appEvents.emit("listing:delete", { listingId: data.listingId }); | |
| }); | |
| // --- 5. DOM Delegation Hook --- | |
| function useDOMDelegation() { | |
| useEffect(() => { | |
| function parseDataAttributes(element: HTMLElement) { | |
| const data: Record<string, any> = {}; | |
| for (const attr of Array.from(element.attributes)) { | |
| if (attr.name.startsWith("data-") && attr.name !== "data-action") { | |
| const key = attr.name | |
| .slice(5) | |
| .replace(/-([a-z])/g, (_, l) => l.toUpperCase()); | |
| data[key] = attr.value; | |
| } | |
| } | |
| return data; | |
| } | |
| function delegatedClickHandler(event: Event) { | |
| const target = event.target as HTMLElement; | |
| if (!target) return; | |
| const actionElement = target.closest("[data-action]") as HTMLElement; | |
| if (actionElement) { | |
| const action = actionElement.dataset.action!; | |
| const data = parseDataAttributes(actionElement); | |
| appEvents.emit("dom:action", { action, element: actionElement, data }); | |
| } | |
| } | |
| document.addEventListener("click", delegatedClickHandler); | |
| return () => { | |
| document.removeEventListener("click", delegatedClickHandler); | |
| }; | |
| }, []); | |
| } | |
| // --- 6. Register Handlers to Event Bus --- | |
| // Modal events | |
| appEvents.on("modal:open", ({ modalId }) => modalHandler.openModal(modalId)); | |
| appEvents.on("modal:close", ({ modalId }) => modalHandler.closeModal(modalId)); | |
| // Listing events | |
| appEvents.on("listing:delete", ({ listingId }) => listingHandler.deleteListing(listingId)); | |
| // DOM action dispatcher | |
| appEvents.on("dom:action", ({ action, element, data }) => { | |
| const handler = actionHandlers.get(action); | |
| if (handler) handler(element, data); | |
| }); | |
| // --- 7. Notification Component --- | |
| function NotificationCenter() { | |
| const [notifications, setNotifications] = useState< | |
| { type: string; message: string; id: number }[] | |
| >([]); | |
| useEffect(() => { | |
| const id = appEvents.on("notification:show", ({ type, message }) => { | |
| setNotifications((prev) => [ | |
| ...prev, | |
| { type, message, id: Date.now() + Math.random() }, | |
| ]); | |
| setTimeout(() => { | |
| setNotifications((prev) => prev.slice(1)); | |
| }, 2000); | |
| }); | |
| return () => appEvents.off("notification:show", id); | |
| }, []); | |
| return ( | |
| <div style={{ position: "fixed", top: 10, right: 10, zIndex: 1000 }}> | |
| {notifications.map((n) => ( | |
| <div | |
| key={n.id} | |
| style={{ | |
| background: n.type === "success" ? "#d4edda" : "#f8d7da", | |
| color: "#155724", | |
| border: "1px solid #c3e6cb", | |
| borderRadius: 4, | |
| marginBottom: 8, | |
| padding: "8px 16px", | |
| minWidth: 200, | |
| }} | |
| > | |
| {n.message} | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| } | |
| // --- 8. Demo UI --- | |
| function DemoTable() { | |
| const [listings, setListings] = useState([ | |
| { id: "101", name: "Listing A" }, | |
| { id: "102", name: "Listing B" }, | |
| { id: "103", name: "Listing C" }, | |
| ]); | |
| useEffect(() => { | |
| const id = appEvents.on("listing:delete", ({ listingId }) => { | |
| setListings((prev) => prev.filter((l) => l.id !== listingId)); | |
| }); | |
| return () => appEvents.off("listing:delete", id); | |
| }, []); | |
| return ( | |
| <table border={1} cellPadding={8} style={{ marginTop: 16 }}> | |
| <thead> | |
| <tr> | |
| <th>Name</th> | |
| <th>Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {listings.map((listing) => ( | |
| <tr key={listing.id}> | |
| <td>{listing.name}</td> | |
| <td> | |
| <button | |
| data-action="delete-listing" | |
| data-listing-id={listing.id} | |
| style={{ color: "red" }} | |
| > | |
| Delete | |
| </button> | |
| </td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| ); | |
| } | |
| function DemoModalTrigger() { | |
| const [modalOpen, setModalOpen] = useState<string | null>(null); | |
| useEffect(() => { | |
| const openId = appEvents.on("modal:open", ({ modalId }) => setModalOpen(modalId)); | |
| const closeId = appEvents.on("modal:close", () => setModalOpen(null)); | |
| return () => { | |
| appEvents.off("modal:open", openId); | |
| appEvents.off("modal:close", closeId); | |
| }; | |
| }, []); | |
| return ( | |
| <> | |
| <button | |
| data-action="open-modal" | |
| data-modal-id="createListingModal" | |
| style={{ marginBottom: 16 }} | |
| > | |
| Open Create Listing Modal | |
| </button> | |
| {modalOpen && ( | |
| <div | |
| style={{ | |
| position: "fixed", | |
| top: 80, | |
| left: "50%", | |
| transform: "translateX(-50%)", | |
| background: "#fff", | |
| border: "1px solid #ccc", | |
| borderRadius: 8, | |
| padding: 24, | |
| zIndex: 100, | |
| minWidth: 300, | |
| }} | |
| > | |
| <h3>{modalOpen}</h3> | |
| <p>This is a modal. You can close it below.</p> | |
| <button data-action="close-modal" data-modal-id={modalOpen}> | |
| Close Modal | |
| </button> | |
| </div> | |
| )} | |
| </> | |
| ); | |
| } | |
| // --- 9. Main Demo Component --- | |
| export default function EventBusDemo() { | |
| useDOMDelegation(); | |
| return ( | |
| <div style={{ fontFamily: "sans-serif", padding: 32 }}> | |
| <h2>Event Bus Demo: Decoupled UI Actions</h2> | |
| <p> | |
| Click the buttons below. All UI actions are handled via a global event bus and a single DOM event listener. | |
| </p> | |
| <DemoModalTrigger /> | |
| <DemoTable /> | |
| <NotificationCenter /> | |
| </div> | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment