Skip to content

Instantly share code, notes, and snippets.

@Herve07h22
Last active Dec 7, 2022
Embed
What would you like to do?
Observable/Observer in React with 65 lines of TypeScript
import { useEffect, useState } from "react";
class EventBus<T> {
_handlers: Set<(t: T) => void> = new Set();
subscribe(handler: (t: T) => void) {
this._handlers.add(handler);
}
unsubscribe(handler: (t: T) => void) {
this._handlers.delete(handler);
}
emit(t: T) {
for (const h of this._handlers.values()) {
h(t);
}
}
}
const watcher = (bus: EventBus<void>) => ({
set: function <T>(obj: T, prop: string | symbol, value: unknown) {
// @ts-ignore
obj[prop] = value;
bus.emit();
return true;
},
});
export type Observable<T extends object> = T & { __bus: EventBus<void> };
export function observable<T extends object>(
obj: T,
nameForDebugging?: string
): Observable<T> {
const bus = new EventBus<void>();
Object.defineProperty(obj, "__bus", {
value: bus,
writable: false,
});
const proxifiedObject = new Proxy<T>(obj, watcher(bus)) as Observable<T>;
if (nameForDebugging) {
Object.defineProperty(window, nameForDebugging, {
value: proxifiedObject,
writable: false,
}); // usefull to watch the subject in the console
}
return proxifiedObject;
}
export function useObserver<T extends object>(obj: Observable<T>) {
useSyncExternalStore(
(onStoreChange) => subscribe(obj)(onStoreChange),
() => JSON.stringify(obj),
() => JSON.stringify(obj)
);
return obj;
}
function subscribe<T extends object>(obj: Observable<T>) {
return (onStoreChange: () => void) => {
const bus = obj.__bus;
bus.subscribe(onStoreChange);
return () => bus.unsubscribe(onStoreChange);
};
}
// Usage :
// 1. Create an instance of your object outside of the React.FC
// const observableObject = observable(myObject)
// 2. In your React.FC, observe the changes and trigger re-renders
// const observer = useObserver(observableObject);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment