Skip to main content

PubSub and GenericPubSub

Lightweight reactive event emitter designed for fine-grained UI reactivity and class-based data models.

GenericPubSub<T> is just a generic version of PubSub, for when you need to explicitly define the notifiable keys.


Overview

PubSub implements a minimal publish/subscribe mechanism that allows any class to notify observers when one or more of its properties change.
It’s the core of the reactivity model used in use-less-react, enabling efficient data flow between stores and UI components without redundant renders.

Define a class extending PubSub

import { PubSub } from '@dxbox/use-less-react/classes';

class Counter extends PubSub {
count = 0;

increment() {
this.count++;
// you can use the notify method from PubSub or a decorator (see next)
this.notify('count');
}
}

const counter = new Counter();

Connect the class to a React component via the useReactiveInstance hook

import { useReactiveInstance } from '@dxbox/use-less-react/hooks';

function CounterComponent({ counter }: { counter: Counter }) {
const { state: count } = useReactiveInstance(
counter,
(instance) => instance.count,
['count'],
);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => counter.increment()}>Increment</button>
</div>
);
}

Try it out in the playground:

If you edit the code of any class inside this sandbox, please hit the "refresh preview" Refresh preview button to apply changes.

Notify changes on attributes

counter.notify("count"); // notify single attribute
sprite.notify("x", "y"); // notify multiple attributes

Subscribe to changes

sprite.subscribe((propNames) => {
// do something with the notified propNames, i.e.
if (propNames.includes("x")) {
const x = sprite.x; // retrieve value
// do something with x
}
});

The subscribe method returns all the notified attributes. It doesn't return values, you have to get them manually if you need them.

Use onNotify

sprite.onNotify(["x"], ({ x }) => {
// do something with x
});

The onNotify method returns all the notified attributes in a key-value object.

Notify a derived value calculated via a get method

Let's say you have a class using a get method like this:

export class Sprite extends PubSub {
private x = 0;
private y = 0;

public get position() {
return { x: this.x, y: this.y };
}

@Notifies("position")
public setPosition(x: number, y: number) {
this.x = x;
this.y = y;
}
}

When setPosition notifies position, everything will work like with a standard class attribute. So you can define derived values, notify them, and let useReactiveInstance know their value has changed, like any other attribute.

Declaring reactive properties

Use makeReactiveProperties to automatically define a reactive setter that notifies a property whenever its value is changed.

class MyStore extends PubSub {
counter: number = 0;
message: string = "Hello";

constructor() {
super();
// Declares which properties should receive a reactive setter
this.makeReactiveProperties('counter', 'message');
}

incrementCounter() {
this.counter += 1; // this.notify("counter") is automatically called!
}
}

It's intended to be used inside the constructor, as the last instruction.

It will throw if used on functions:

class MyStore extends PubSub {
counter: number = 0;

constructor() {
super();
this.makeReactiveProperties('incrementCounter');
}

incrementCounter() {
this.counter += 1;
}
}

const store = new MyStore();

will throw Cannot make reactive keys ["incrementCounter"]: functions cannot be made reactive.

Batching notifications

Use batchNotifications for notifying many properties as one single call to notify

await instance.batchNotifications(() => {
someActionThatNotifiesX(); // calls instance.notify("y")
await someOtherActionThatNotifiesY(); // calls instance.notify("y")

if (someCondition) { // if a condition is met
await someActionThatNotifiesZ(); // calls instance.notify("z")
}
});
// at the end of the callback function passed to batchNotifications,
// all the pending notifications are triggered together as instance.notify("x", "y", ...)

Notes

  • PubSub is agnostic to the UI layer — it can be used in React, Node.js, or any JS runtime.
  • Use decorators like @Notifies, @DependsOn and @BatchNotifications to integrate smoothly with reactivity systems.
  • Combine with the @ImmutableClass decorator for safer state updates in development.