@skarlatov/react-event-bus
v2.1.2
Published
A simple implementation of the event pattern for React and React Native apps.
Downloads
454
Maintainers
Readme
React Event Bus :zap:
Description
This package is an implementation of the event pattern. It allows direct, decoupled communication between entities (components, stores, actions etc.) in React or React Native applications.
Motivation
Using events makes it easier for components to react to changes without indirectly using central state or props with useEffect. This is a very common source of performance issues and other bugs in React apps.
Drawbacks
Relying too heavily on events for state management can result in code that is hard to follow especially in pure Javascript projects. Also beware of event storms (unintended cascade of events or infinite event loops).
Installation
Using npm
npm i @skarlatov/react-event-bus
Using yarn
yarn add @skarlatov/react-event-bus
Basic usage :pizza:
1. Context setup
import { uniqueId } from "lodash";
import { EventBusProvider } from "@skarlatov/react-event-bus";
export function App() {
/*
The provider needs a function that creates unique ids to keep
track of its subscriptions internally. The function is passed
as a prop so that the event bus library doesn't have any other
dependencies except React.
*/
return (
<EventBusProvider createUniqueId={uniqueId}>
{/* The rest of your root app code is here */}
</EventBusProvider>
);
}
2. Events contract
export interface PizzaEvents {
"pizza-ordered"?: (pizzaName: string) => void;
}
3. Event emitter
import React, { useCallback } from "react";
import { useEventBus } from "@skarlatov/react-event-bus";
import { PizzaEvents } from "./contracts";
export function PizzeriaWaiter() {
const { raiseEvent } = useEventBus<PizzaEvents>();
const onOrderPizza = useCallback(() => {
raiseEvent("pizza-ordered", "New York");
}, [raiseEvent]);
return (
<div>
<div>Pizzeria Waiter</div>
<button onClick={onOrderPizza}>Order pizza!</button>
</div>
);
}
4. Event consumer
import React, { useCallback, useMemo } from "react";
import { useEventBus } from "@skarlatov/react-event-bus";
import { PizzaEvents } from "./contracts";
export function PizzeriaKitchen() {
const onPizzaOrdered = useCallback((name: string) => {
console.log(`We have a new pizza ${name} to make! Chop chop!`)
}, []);
// It is very important that the eventSubscriptions object
// has a stable reference. Otherwise the event bus will
// subscribe and unsubscribe from the event on every render.
const eventSubscriptions: PizzaEvents = useMemo(() => ({
"pizza-ordered": onPizzaOrdered,
}), [onPizzaOrdered]);
useEventBus<PizzaEvents>({ eventSubscriptions });
return (
<div>
Pizzeria Kitchen
</div>
);
}
Usage outside React components
The event bus can be accessed outside React components by using the setGlobalEventBusRef utility function and the contextCreated prop of the provider.
import { uniqueId } from "lodash";
import { EventBusProvider, setGlobalEventBusRef } from "@skarlatov/react-event-bus";
export function App() {
return (
<EventBusProvider
contextCreated={setGlobalEventBusRef}
createUniqueId={uniqueId}>
{/* The rest of your root app code is here */}
</EventBusProvider>
);
}
The global reference can be used to raise events from anywhere including stores and actions with the raiseEvent utility function. This function is safe in the sense that if the global ref is not initialized yet your app will not crash. Just the event won't be raised and a warning will be printed in the console.
For the sake of the example we update our PizzaEvents contract with a new 'pizza-ready' event.
export interface PizzaEvents {
"pizza-ordered"?: (pizzaName: string) => void;
// The new event
"pizza-ready"?: (pizzaName: string) => void;
}
Now we can raise the event without the useEventBus hook like so:
import { useCallback } from 'react';
import { globalEventBus } from "@skarlatov/react-event-bus";
import { PizzaEvents } from "./contracts";
const { raiseEvent } = globalEventBus<PizzaEvents>();
export function PizzeriaKitchen() {
const onPizzaReady = useCallback((name: string) => {
raiseEvent("pizza-ready", name);
}, []);
return (
<div>
Pizzeria Kitchen
<button onClick={() => onPizzaReady("New York")}>Pizza ready!</button>
</div>
);
}