TS Bus
A lightweight JavaScript/TypeScript event bus to help manage your application architecture.
Why did I write this?
I wanted a system that
- Is framework agnostic.
- Could enable micro-frontends / microlithic architecture.
- Can easily use React hooks to reduce state.
- Does not conflate eventing with state management.
- Has really good TypeScript support.
Installation
Use your favourite npm client to install ts-bus. Types are included automatically.
Npm:
npm install ts-bus
Yarn:
yarn add ts-bus
Usage
1. Declare events
Create your EventBus globally somewhere:
import { EventBus } from "ts-bus";
export const bus = new EventBus();
Next create some Events:
import { defineEvent } from "ts-bus";
type FirstEvent = {
type: "FIRST_EVENT";
payload: {
id: string;
label: string;
};
};
export const firstEvent = defineEvent<FirstEvent>("FIRST_EVENT");
TIP
I find putting the event type inline leads to more concise event definitions:
export const otherEvent = defineEvent<{
type: "OTHER_EVENT";
payload: { label:string }
};>("OTHER_EVENT");
2. Subscription
Ok. Let's subscribe to our events
import { firstEvent, otherEvent } from "./event";
import { bus } from "./bus";
const unsubscribe = bus.subscribe(firstEvent, event => {
const { id, label } = event.payload;
doSomethingWithFirstEvent({ id, label });
});
setTimeout(unsubscribe, 20 * 1000);
2. Publishing events
Now let's publish our events somewhere
import { firstEvent, otherEvent } from "./events";
import { bus } from "./bus";
function handleButtonClick() {
bus.publish(firstEvent({ id: "my-id", label: "This is an event" }));
}
function handleButtonRightClick() {
bus.publish(otherEvent({ label: "You right clicked" }));
}
TIP:
If you want to avoid the direct dependency with your event creator (lets say because of bounded context or micro-frontends) like in Redux you can use the event object:
bus.publish({
type: "KICKOFF_SOME_PROCESS",
payload: props.data
});
Thats the basics of the ts-bus
Usage with React
Included with ts-bus
are some React hooks and helpers.
BusProvider
Wrap your app using the BusProvider
import React from "react";
import App from "./App";
import { EventBus } from "ts-bus";
import { BusProvider } from "ts-bus/react";
const bus = new EventBus();
export default () => (
<BusProvider value={bus}>
<App />
</BusProvider>
);
useBus
Access the bus instance with useBus
import { useBus } from "ts-bus/react";
import { kickoffSomeProcess } from "./my-events";
function ProcessButton(props) {
const bus = useBus();
const handleClick = React.useCallback(() => {
bus.publish(kickoffSomeProcess(props.data));
}, [bus]);
return <Button onClick={handleClick}>Go</Button>;
}
useBusReducer
This can be used as a much more flexible alternative to Redux because not every event requires a corresponding state change. Also you can hook multiple frameworks together and create microfrontends with this technique.
import { useBusReducer } from "ts-bus/react";
function Main(props: Props) {
const state = useBusReducer(
produce((state, action) => {
switch (action.type) {
case "TASK_MOVED": {
return state;
}
case "TASK_CREATED": {
return state;
}
case "TASK_UPDATED": {
return state;
}
default:
return state;
}
}),
initState
);
return <MyApp state={state}>{children}</MyApp>;
}