EL

Eljan Simuratli

9/5/2025

Observer Pattern: From Class-Based Implementation to React Event Bus

2 Mins Read
Observer Pattern: From Class-Based Implementation to React Event Bus

The Observer pattern is also called the publish/subscribe pattern in short. It means that when a change happens, we notify all observers that have subscribed.

When we can use it:

  • Event flow (button clicked, some information comes from the socket)
  • Broadcasting changes from the store to components.
  • If u have loosely-coupled structure

When we cannot use:

  • Simple one-time interaction between two modules (can become overly complex)
  • If chained notifications create performance/observability issues

I will give you 3 examples for usage of Observer pattern.

Class Based:

classbased

class Subject {
  constructor() {
    this.observers = new Set();
  } 
  subscribe(fn) {
    this.observers.add(fn);
    return () => this.observers.delete(fn); // unsubscribe 
  }
  notify(payload) {
    for (const fn of this.observers) fn(payload);
  }
}

// kullanım
const subject = new Subject();
const log1 = (x) => console.log("obs1:", x);
const log2 = (x) => console.log("obs2:", x);

const off1 = subject.subscribe(log1);
subject.subscribe(log2);

subject.notify("First Message");
off1(); // obs1 removed
subject.notify("Second Message");

Step 1: We create observers with Set()Why we choose Set(); because it removes duplicates and the delete and add operations are faster than others. We store fn(listener function) in here.

Step 2: Creating a subscribe function, it also includes an unsubscribe function:

subscribe(fn) {
    this.observers.add(fn);
    return () => this.observers.delete(fn); // unsubscribe 
  }

Step 3: The Notify function helps us to send the payload to all observers:

notify(payload) {
    for (const fn of this.observers) fn(payload);
 }

Usage:

We call the Subject class for a new Set of functions:

const subject = new Subject();

We create 2 log function for testing it:

const log1 = (x) => console.log("obs1:", x);
const log2 = (x) => console.log("obs2:", x);

Now Set is look like : { log1, log2 }. When we run subject.notify(“ilk mesaj”) it run 2 log function inside set:

obs1: First Message
obs2: First Message

When we run off1() it only remove (unsubscribe) log1 from Setnow Setis {log2} only.

Minimal event bus:

export function createEventBus() {
  const listeners = new Set();
  const subscribe = (fn) => (listeners.add(fn), () => listeners.delete(fn));
  const publish = (data) => listeners.forEach((fn) => fn(data));
  return { subscribe, publish };
}

// usage
const bus = createEventBus();
const off = bus.subscribe((d) => console.log("got:", d));
bus.publish({ type: "LOGIN", user: "eljan" });
off();

In React:

import React from "react";

const listeners = new Set();
const publish = (d) => listeners.forEach((fn) => fn(d));
const subscribe = (fn) => (listeners.add(fn), () => listeners.delete(fn));

function Publisher() {
  return <button onClick={() => publish("butona tıklandı!")}>Publish</button>;
}

function Subscriber({ label }) {
  const [msg, setMsg] = React.useState("-");
  React.useEffect(() => subscribe(setMsg), []);
  return <p>{label}: {msg}</p>;
}

export default function App() {
  return (
    <div>
      <Publisher />
      <Subscriber label="A" />
      <Subscriber label="B" />
    </div>
  );
}
You can get Other Patterns from my Github repo.