<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sambalicious</title>
    <description>The latest articles on DEV Community by Sambalicious (@sambalicious).</description>
    <link>https://dev.to/sambalicious</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F399300%2F868dd8b7-9c36-4f50-9edc-8b423f50dec1.jpeg</url>
      <title>DEV Community: Sambalicious</title>
      <link>https://dev.to/sambalicious</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sambalicious"/>
    <language>en</language>
    <item>
      <title>Type-Safe Compound Components in 2026 – The Generic Factory Pattern I Actually Use</title>
      <dc:creator>Sambalicious</dc:creator>
      <pubDate>Sat, 07 Feb 2026 11:05:40 +0000</pubDate>
      <link>https://dev.to/sambalicious/type-safe-compound-components-in-2026-the-generic-factory-pattern-i-actually-use-4dg0</link>
      <guid>https://dev.to/sambalicious/type-safe-compound-components-in-2026-the-generic-factory-pattern-i-actually-use-4dg0</guid>
      <description>&lt;p&gt;&lt;strong&gt;How I finally got real type safety on children values without losing the beautiful dot API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkhms2668ces7tdfmlql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkhms2668ces7tdfmlql.png" alt="react-typescript" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compound components are awesome for building flexible UI primitives — think &lt;code&gt;&amp;lt;Tabs&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Accordion&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;RadioGroup&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;lt;Popover&amp;gt;&lt;/code&gt; — where the consumer controls the layout and composition.&lt;/p&gt;

&lt;p&gt;But flexibility often meant &lt;strong&gt;zero type safety&lt;/strong&gt; on children or values.&lt;/p&gt;

&lt;p&gt;I used to write stuff like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;RadioGroup value={selected} onChange={setSelected}&amp;gt;
  &amp;lt;RadioItem value="draft"&amp;gt;Draft&amp;lt;/RadioItem&amp;gt;
  &amp;lt;RadioItem value="published"&amp;gt;Published&amp;lt;/RadioItem&amp;gt;
  &amp;lt;RadioItem value={123}&amp;gt;Wait what? {/* ← TypeScript is silent 😭 */}
&amp;lt;/RadioItem&amp;gt;
&amp;lt;/RadioGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wanted TypeScript to actually yell at me when the value type was wrong, while keeping the clean dot-notation DX everyone loves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;StatusRadio.RadioGroup value={status} onValueChange={setStatus}&amp;gt;
  &amp;lt;StatusRadio.RadioItem value="draft"&amp;gt;...&amp;lt;/StatusRadio.RadioItem&amp;gt;
  {/* value={123} → red squiggle! */}
&amp;lt;/StatusRadio.RadioGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reading TkDodo’s excellent post “Building Type-Safe Compound Components” (Jan 2, 2026), I extended the ideas into a tiny generic factory. It’s now my go-to for typed item groups (radios, tabs, segmented controls, steppers…).&lt;/p&gt;

&lt;p&gt;Two Flavors of Compound Components&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Max layout freedom (Card/Dialog style)
Free-form children, arbitrary order:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Card&amp;gt;
  &amp;lt;CardHeader&amp;gt;Title&amp;lt;/CardHeader&amp;gt;
  &amp;lt;CardContent&amp;gt;Anything goes&amp;lt;/CardContent&amp;gt;
  &amp;lt;CardFooter&amp;gt;Actions&amp;lt;/CardFooter&amp;gt;
&amp;lt;/Card&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→ Use simple context + sub-components. Super common in shadcn/ui, Radix, etc. (Not the focus here).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Strict typed children (Radio/Tabs/Steps style)
Predictable items + strong value typing — factory time!
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Status = "draft" | "published" | "archived";

const StatusRadio = createTypedRadioGroup&amp;lt;Status&amp;gt;();

&amp;lt;StatusRadio.RadioGroup value={status} onValueChange={setStatus}&amp;gt;
  &amp;lt;StatusRadio.RadioItem value="draft"&amp;gt;
    &amp;lt;StatusRadio.RadioIndicator /&amp;gt;
    Draft
  &amp;lt;/StatusRadio.RadioItem&amp;gt;
  {/* value={123} → compile error */}
&amp;lt;/StatusRadio.RadioGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Reusable Factory Pattern&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createContext, useContext } from "react";
import {
  RadioGroup as BaseRadioGroup,
  Radio,
  mergeProps,
} from "@base-ui/react";
import { radioGroupRootVariants, radioItemVariants, RadioIndicator } from "../ui/radio";

type RadioGroupContextValue = {
  size?: "sm" | "md" | "lg" | null;
  variant?: "default" | "outline" | null;
};

export function createTypedRadioGroup&amp;lt;T&amp;gt;() {
  const RadioGroupContext = createContext&amp;lt;RadioGroupContextValue | null&amp;gt;(null);

  function TypedRadioGroup({
    size,
    variant,
    orientation,
    className,
    children,
    ...props
  }: Omit&amp;lt;React.ComponentProps&amp;lt;typeof BaseRadioGroup&amp;gt;, "children" | "className"&amp;gt; &amp;amp; {
    size?: "sm" | "md" | "lg";
    variant?: "default" | "outline";
    orientation?: "horizontal" | "vertical";
    className?: string;
    children: React.ReactNode;
  }) {
    const ctxValue = { size: size ?? null, variant: variant ?? null };

    return (
      &amp;lt;RadioGroupContext.Provider value={ctxValue}&amp;gt;
        &amp;lt;BaseRadioGroup
          className={radioGroupRootVariants({ size, orientation })}
          {...props}
        &amp;gt;
          {children}
        &amp;lt;/BaseRadioGroup&amp;gt;
      &amp;lt;/RadioGroupContext.Provider&amp;gt;
    );
  }

  function TypedRadioItem({
    value,
    size: itemSize,
    className,
    children,
    ...props
  }: { value: T; size?: "sm" | "md" | "lg" } &amp;amp; Omit&amp;lt;
    React.ComponentProps&amp;lt;typeof Radio.Root&amp;gt;,
    "value" | "children"
  &amp;gt;) {
    const context = useContext(RadioGroupContext);
    if (!context) {
      throw new Error("RadioItem must be used inside RadioGroup");
    }

    const finalSize = itemSize ?? context.size ?? "md";

    const merged = mergeProps({ className }, {
      className: radioItemVariants({ size: finalSize }),
    });

    // Adapt value to string/number as needed by your base library
    return (
      &amp;lt;Radio.Root value={String(value)} className={merged.className} {...props}&amp;gt;
        {children}
      &amp;lt;/Radio.Root&amp;gt;
    );
  }

  return {
    RadioGroup: TypedRadioGroup,
    RadioItem: TypedRadioItem,
    RadioIndicator,
  } as const;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: If your headless base (Radix/Ark/Base UI) supports generic values natively, drop the String(value) cast.&lt;/p&gt;

&lt;p&gt;Real-World Examples: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Status Picker (string literals)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Status = "draft" | "published" | "archived";

const StatusRadio = createTypedRadioGroup&amp;lt;Status&amp;gt;();

export function StatusPicker() {
  const [status, setStatus] = useState&amp;lt;Status&amp;gt;("draft");

  return (
    &amp;lt;StatusRadio.RadioGroup
      value={status}
      onValueChange={setStatus}
      size="md"
      variant="default"
    &amp;gt;
      &amp;lt;StatusRadio.RadioItem value="draft"&amp;gt;
        &amp;lt;StatusRadio.RadioIndicator /&amp;gt;
        Draft
      &amp;lt;/StatusRadio.RadioItem&amp;gt;
      &amp;lt;StatusRadio.RadioItem value="published"&amp;gt;
        &amp;lt;StatusRadio.RadioIndicator /&amp;gt;
        Published
      &amp;lt;/StatusRadio.RadioItem&amp;gt;
      &amp;lt;StatusRadio.RadioItem value="archived"&amp;gt;
        &amp;lt;StatusRadio.RadioIndicator /&amp;gt;
        Archived
      &amp;lt;/StatusRadio.RadioItem&amp;gt;
    &amp;lt;/StatusRadio.RadioGroup&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Star Rating (numbers)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const RatingRadio = createTypedRadioGroup&amp;lt;number&amp;gt;();

&amp;lt;RatingRadio.RadioGroup value={rating} onValueChange={setRating} orientation="horizontal"&amp;gt;
  {[1, 2, 3, 4, 5].map((n) =&amp;gt; (
    &amp;lt;RatingRadio.RadioItem key={n} value={n}&amp;gt;
      &amp;lt;RatingRadio.RadioIndicator /&amp;gt;
      {n} ★
    &amp;lt;/RatingRadio.RadioItem&amp;gt;
  ))}
&amp;lt;/RatingRadio.RadioGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Object-based (real-world use case)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface Option {
  id: string;
  label: string;
  description: string;
}

const OptionRadio = createTypedRadioGroup&amp;lt;Option&amp;gt;();

export function OptionPicker() {
  const options: Option[] = [
    { id: "1", label: "Option 1", description: "First option" },
    { id: "2", label: "Option 2", description: "Second option" },
    { id: "3", label: "Option 3", description: "Third option" },
  ];

  const [selected, setSelected] = useState&amp;lt;Option&amp;gt;(options[0]);

  return (
    &amp;lt;OptionRadio.RadioGroup value={selected} onValueChange={setSelected}&amp;gt;
      {options.map((option) =&amp;gt; (
        &amp;lt;OptionRadio.RadioItem key={option.id} value={option}&amp;gt;
          &amp;lt;OptionRadio.RadioIndicator /&amp;gt;
          &amp;lt;div&amp;gt;
            &amp;lt;div className="font-medium"&amp;gt;{option.label}&amp;lt;/div&amp;gt;
            &amp;lt;div className="text-sm text-gray-500"&amp;gt;{option.description}&amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/OptionRadio.RadioItem&amp;gt;
      ))}
    &amp;lt;/OptionRadio.RadioGroup&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trade-offs – When to Use (or Skip) This&lt;br&gt;
&lt;strong&gt;Use the factory when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong value type safety is non-negotiable&lt;/li&gt;
&lt;li&gt;Children order is predictable (list/group items)&lt;/li&gt;
&lt;li&gt;You want that sweet dot-notation DX&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Skip it&lt;/strong&gt; (use plain context + sub-components) when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1. You need a completely free-form layout/order&lt;/li&gt;
&lt;li&gt;2. Building low-level primitives (just use Radix/Ark/Base UI directly)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Final Thoughts&lt;br&gt;
This little factory has been my default for typed groups since 2026 simple, type-safe, minimal boilerplate.&lt;br&gt;
Ideas to level it up:&lt;/p&gt;

&lt;p&gt;Propagate disabled from group → items&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add defaultValue support&lt;/li&gt;
&lt;li&gt;Generalize for &lt;code&gt;&amp;lt;Tabs&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Accordion&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;RadioGroup&amp;gt;&lt;/code&gt; etc.&lt;/li&gt;
&lt;li&gt;Package it as a tiny npm lib&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Steal it, tweak it, ship with it.&lt;br&gt;
What’s your go-to pattern for typed compound components in 2026? Drop a comment — or hit me up on X: @sambalicious_ 🚀&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Scalable Notifications in React with Pub/Sub (No State Store)</title>
      <dc:creator>Sambalicious</dc:creator>
      <pubDate>Thu, 29 Jan 2026 20:44:41 +0000</pubDate>
      <link>https://dev.to/sambalicious/scalable-notifications-in-react-with-pubsub-no-state-store-5gfp</link>
      <guid>https://dev.to/sambalicious/scalable-notifications-in-react-with-pubsub-no-state-store-5gfp</guid>
      <description>&lt;p&gt;Recently, I needed to add a flexible, scalable notification system to a large-scale React/Next.js application. The backend provided a handy endpoint that returns all pending notifications at once. My initial thought was simple: fetch them globally and render a single Notification component that switches UI based on the notification type.&lt;/p&gt;

&lt;p&gt;Quickly, two problems emerged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The endpoint could return multiple notifications at once—how do I handle queuing and displaying them without chaos?&lt;/li&gt;
&lt;li&gt;For custom notification types (e.g., interactive modals, special banners), the single component would become bloated with conditionals and logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After some research, I landed on a classic design pattern perfectly suited for this: Publisher/Subscriber (Pub/Sub).&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Pub/Sub?
&lt;/h2&gt;

&lt;p&gt;Pub/Sub is an asynchronous messaging pattern that decouples the message sender (Publisher) from the receivers (Subscribers). Publishers broadcast events to a central event bus (or broker) without knowing who (if anyone) is listening. Subscribers register interest in specific event types and react only when relevant messages arrive.&lt;/p&gt;

&lt;p&gt;It’s like a radio station: the broadcaster sends signals blindly, and anyone tuned to the right frequency picks them up.&lt;/p&gt;

&lt;p&gt;The publisher sends messages to the topic without knowing who will receive them through a broker (Event bus) to subscribers, who receive only relevant messages that they subscribed to. This pattern allows for independent, asynchronous component operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Principles &amp;amp; Benefits
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loose Coupling&lt;/strong&gt;: Publishers and subscribers don’t know about each other—no direct imports or references. They are not directly connected and do not need to know each other's identities or locations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Easily add more subscribers to a topic as your app grows; the bus handles distribution. The system can handle a large volume of events by adding more subscribers without impacting performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asynchronous Communication&lt;/strong&gt;: Publishers fire events and move on—no waiting for responses. They can send messages and continue their work without blocking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Many-to-Many Relationships&lt;/strong&gt;: One publisher can reach many subscribers, and one subscriber can listen to many publishers.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When to Use It in React/Next.js
&lt;/h2&gt;

&lt;p&gt;Pub/Sub is ideal for fire-and-forget events like notifications, analytics tracking, theme changes, or global modals. Avoid it for complex shared state—use Zustand, Redux, Jotai, or Context instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation: Lightweight Notification Event Bus
&lt;/h2&gt;

&lt;p&gt;Let’s build it step by step.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Event Bus
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EventKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Registration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PasswordReset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Extend with your notification types&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notificationEventBus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;eventKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventKey&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Return unsubscribe function&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventKey&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;eventKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callbacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s2"&gt;`Error in notificationEventBus subscriber for event "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventKey&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;". Check the implementation of this subscriber to handle the error appropriately.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Helper function to publish all notifications at once&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publishNotifications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NotificationData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PopupNotificationDetails&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;notificationEventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Note: &lt;code&gt;NotificationData&lt;/code&gt; and &lt;code&gt;PopupNotificationDetails&lt;/code&gt; are placeholders—define them based on your notification structure, e.g., &lt;code&gt;{ type: EventKey; message: string; }&lt;/code&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  2. React Hook for Safe Subscriptions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EventKey&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@soar/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Adjust import based on your types&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;notificationEventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../helpers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useNotificationEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="na"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(
  eventKey: EventKey,
  handler: (data: T) =&amp;gt; void,
) =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handlerRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Keep handler up-to-date without re-subscribing&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;handlerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stableHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;handlerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notificationEventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;eventKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;stableHandler&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;eventKey&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Usage Example: Listening for a Specific Notification
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useNotificationEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OrderDelivered&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Do whatever with the notification, e.g., show a toast or modal&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Fetch and Publish Notifications in Your Component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example: Fetch and publish&lt;/span&gt;
&lt;span class="c1"&gt;// For one-time fetch; use polling or WebSockets for real-time.&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/notifications&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;publishNotifications&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Your helper function&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-World Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Notifications&lt;/strong&gt;: Publish per type—each subscriber handles its own UI cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Notifications&lt;/strong&gt;: Create dedicated subscriber components instead of one giant switch statement.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Pub/Sub gave me exactly what I needed: decoupled, scalable notifications without bloating components or pulling in heavy state management. It’s simple, performant, and easy to reason about.&lt;/p&gt;

&lt;p&gt;Try it in your Next.js/React app—start with this event bus and hook, then expand to toasts, modals, or analytics. I made a little POC for this; fork the code and tweak it! Let me know how it works for you. 🚀&lt;/p&gt;

&lt;p&gt;Questions or improvements? Hit me up on X: @sambalicious_&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>designpatterns</category>
    </item>
  </channel>
</rss>
