React
Postboy integrates naturally with React’s component model, hooks, and RxJS-based subscriptions. This guide focuses on React-specific integration: how to create the bus, how to activate registrators at the right time, how to use React Context when a singleton is not enough, and how to avoid common lifecycle issues with effects, Strict Mode, and server-side rendering.
For general Postboy concepts, see:
Installation
Install Postboy with npm:
Postboy is published in major lines that follow the RxJS generation used by your project:
Postboy branch | RxJS version | Typical usage |
|---|---|---|
|
| Older RxJS projects |
|
| Current projects using RxJS 7 |
|
| Upcoming line for projects using RxJS 8 |
For most current React projects using RxJS 7, use the 3.x line.
When your project moves to RxJS 8, use the 4.x line:
For more details about versioning, see Versions.
React integration model
React does not have a built-in dependency injection container. Because of that, Postboy integration is usually based on one of two patterns:
A module-level singleton bus.
The simplest option. It works well for many client-side React applications.A React Context provider.
A more explicit option. It is useful when you need isolated bus instances, SSR-aware setup, microfrontend boundaries, or easier replacement in tests.
Registrators still play the same role as in other environments:
up()activates registrations;down()cleans lifecycle-bound subscriptions and registrations.
In React, the main task is to align up() and down() with the correct React lifetime.
The simplest client-side setup
For a regular client-side React application, the simplest setup is:
create one
PostboyServiceinstance;create one root registrator;
activate the root registrator before rendering the React tree;
use
useEffectfor component subscriptions.
This avoids the most common error:
That error usually means that a component tried to subscribe or publish before the corresponding message channel was registered.
Step 1. Create a singleton bus
Create the bus in a separate module:
This singleton becomes the application-wide message bus.
Do not create the bus inside a React component body:
React components can render many times. The bus must have a stable lifetime.
Step 2. Create a root registrator
A root registrator is useful for application-wide communication: authentication state, theme changes, global notifications, diagnostics, and other cross-cutting concerns.
Keep the root registrator small. It should contain only messages and handlers that are truly global.
For details about registration methods, see PostboyAbstractRegistrator.
Step 3. Activate the root registrator before rendering React
If child components subscribe immediately after mounting, activating the root registrator in App.useEffect() may be too late.
A reliable first setup is to activate the root registrator before calling render(...).
The order is explicit:
create the bus;
activate the root registrator;
render the React application;
components can safely subscribe to registered root messages.
This is the recommended minimal setup for a simple client-side SPA.
If you use hot module replacement, tests, or non-standard application bootstrap, make sure to call rootRegistrator.down() when the application is disposed.
Step 4. Subscribe from a component
Postboy subscriptions are RxJS subscriptions. Use useEffect and unsubscribe in the cleanup function.
Another component can publish the event from an event handler:
Do not call fire(...), fireCallback(...), or other side-effecting bus methods during render. Use event handlers, effects, or application services.
Why not just use useLayoutEffect in App?
It may be tempting to activate the root registrator inside App with useLayoutEffect:
This can work in some cases because useLayoutEffect runs earlier than useEffect. However, it should not be the default solution for Postboy registration.
Prefer activating global registrations before rendering the React tree when:
root messages are used by many child components;
child components subscribe immediately in
useEffect;you want a predictable bootstrap order;
you are building a simple client-side SPA.
Use useLayoutEffect only when you specifically need React layout timing. Postboy registration is usually an application bootstrap concern, not a layout concern.
Registration order matters
Postboy channels and executor handlers must be registered before they are used.
If a component or service-like module calls fire, sub, fireCallback, or exec before the corresponding registration exists, Postboy can throw an error such as:
In React this usually means one of the following:
the root registrator was never created;
registrator.up()was not called;a component subscribed before the registrator was activated;
a message was registered in a feature registrator, but used outside that feature scope;
the bus was created more than once;
the message class was imported through different runtime paths;
code used the bus during render before setup was complete.
The safe rule is simple:
create the bus once;
create the registrator for the correct scope;
call
up();only then publish, subscribe, call callbacks, or execute handlers;
call
down()when the scope ends.
For more about this lifecycle model, see Lifecycle concepts.
When to use React Context
The singleton setup is intentionally simple. It is a good starting point for many browser-only applications.
Use React Context when you need more control over bus ownership.
Context is useful for:
SSR frameworks;
tests that replace the bus;
multiple independent buses;
isolated widgets;
microfrontends;
feature-level bus ownership;
avoiding module-level singletons.
With Context, you do not import a global postboy instance directly. Instead, React provides the bus through a PostboyProvider, and components access it through a hook.
Creating PostboyContext
Create a context module:
PostboyProvider creates one bus for the subtree inside the provider.
usePostboy() is a convenience hook that gives any child component access to that bus.
Using PostboyProvider
Wrap your application with PostboyProvider.
Now any component under PostboyProvider can access the bus:
However, PostboyProvider only creates and provides the bus. It does not automatically register messages. For that, you still need a registrator.
Root registrator with Context
When using Context, the root registrator should receive the bus from Context instead of importing a singleton.
This file defines three things:
RootRegistrator— registers global messages and handlers.RootRegistratorScope— activates the registrator and renders children only after registration is ready.PostboyRoot— combinesPostboyProviderandRootRegistratorScope.
The ready flag is important. It prevents child components from mounting before the root registrator has activated its channels.
Using PostboyRoot
Now wrap your application with PostboyRoot instead of using PostboyProvider directly.
The application tree is now created in this order:
PostboyRootrendersPostboyProvider;PostboyProvidercreates aPostboyService;RootRegistratorScopereceives that service throughusePostboy();RootRegistratorScopecallsregistrator.up();after activation, it renders
App;child components can safely subscribe to root messages.
This is the Context-based equivalent of activating a singleton registrator before rendering the React tree.
Using the bus from components with Context
When Context is used, do not import a singleton postboy. Use usePostboy().
A publisher looks similar:
The important rule is consistency:
singleton setup → import
postboy;Context setup → use
usePostboy().
Do not mix both patterns in the same scope unless you intentionally use multiple buses.
Choosing between singleton and Context
Use the singleton approach when:
the application is a simple client-side SPA;
one global bus is enough;
you want minimal setup;
SSR is not involved;
tests can work with module-level imports.
Use the Context approach when:
you use SSR or Next.js;
you need one bus per rendered tree;
you want to replace the bus in tests;
you build isolated widgets;
you work with microfrontends;
you need multiple independent communication scopes.
For a first React integration, start with the singleton approach. Move to Context when the application needs explicit bus ownership.
Feature registrators
Feature-specific communication should usually be registered by a feature registrator, not by the root registrator.
This is useful for:
route components;
dialogs;
feature pages;
dashboards;
embedded widgets;
microfrontend entry points;
temporary workflows.
With Context:
The scope component ensures that feature children are rendered only after the feature registrator is active.
With a singleton bus, the same idea can be implemented by importing postboy instead of using usePostboy().
A small helper hook for registrators
If you use feature registrators often, you can create a small hook.
Usage:
This pattern is optional. It can reduce repetition, but the explicit scope component is often easier to understand at first.
Subscriptions in React components
Postboy subscriptions are RxJS subscriptions. Manage them like normal React effect resources.
This is standard React cleanup. Postboy does not require a React-specific unsubscribe mechanism.
For general message subscription patterns, see Generic messages and Callback messages.
Do not use the bus during render
React render should stay pure. Avoid calling methods such as fire, fireCallback, or side-effecting exec calls directly during render.
Avoid this:
Prefer effects or event handlers:
Event handlers are also safe places for publishing messages:
The same rule applies to fireCallback: call it in an effect, event handler, or application service layer — not as part of render.
React Strict Mode
In development, React Strict Mode may intentionally mount, unmount, and mount components again to detect unsafe side effects.
This can expose lifecycle bugs such as:
duplicate registrations;
missing cleanup;
subscriptions that are not unsubscribed;
registrators that call
up()but never calldown().
Always pair up() with down() in effect cleanup:
If a setup action must run only once for the whole application lifetime, consider activation before React rendering. Do this only for truly global setup and keep it predictable for tests.
Strict Mode is not a problem by itself. It helps reveal lifecycle mistakes earlier.
Server-side rendering and Next.js
When using SSR frameworks such as Next.js, be careful with module-level singletons.
A module singleton can be shared between requests on the server. This may lead to data leaking between users if request-specific data is stored in the bus or in registered handlers.
For SSR applications, prefer Context-based bus creation.
In Next.js App Router, client-side Postboy usage should live in Client Components:
Then use it from your layout:
Use a module singleton only when you are sure the code runs only in the browser or does not store request-specific state.
Root vs feature scope
A practical React rule:
use one singleton bus for simple client-side applications;
use Context when you need isolated or SSR-safe bus instances;
use a root registrator only for global communication;
use feature registrators for routes, pages, dialogs, widgets, and temporary UI areas;
call
up()when the scope starts;call
down()when the scope ends;render feature children only after the feature registrator is ready if they subscribe immediately.
Use root scope for:
authentication;
global notifications;
application configuration;
theme;
global error events;
application-wide diagnostics.
Use feature scope for:
page-specific communication;
dialog-specific communication;
route interactions;
editor state;
feature-local commands and queries;
temporary workflows.
This keeps React component ownership and Postboy lifecycle aligned.
Suggested React project structure
One possible structure is:
The exact structure is up to your application, but the important rule is ownership:
global messages should be easy to find globally;
feature messages should live near the feature that owns them;
registrators should be located close to the scope they activate;
hooks and providers should be grouped in a clear integration layer.
For naming conventions and message roles, see Message roles.
Common React mistakes
Creating the bus inside a component render
This creates unstable bus instances and can break subscriptions.
Create the bus as a module singleton or through a memoized provider.
Mixing singleton and Context accidentally
If the registrator uses a singleton bus, but components use a Context bus, they are talking to different Postboy instances.
Choose one pattern for a scope:
singleton imports everywhere;
or Context with
usePostboy()everywhere.
Calling fire or fireCallback during render
Render should be pure. Use useEffect, event handlers, or service functions.
Activating a root registrator too late
If children subscribe in their effects, a parent effect may be too late depending on your tree and timing assumptions.
For simple global setup, activate the root registrator before rendering React. For Context setup, use a PostboyRoot that renders children only after the registrator is ready.
Activating a feature registrator too late
If a component subscribes before the feature registrator is activated, the message channel may not exist yet.
Use a feature scope component and render feature children only after up() has completed.
Forgetting cleanup in useEffect
Always unsubscribe subscriptions and call registrator.down() in cleanup.
Registering all messages in the root registrator
This makes cleanup harder and turns the root registrator into a global dependency hub.
Use feature registrators for feature-owned messages.
Ignoring Strict Mode behavior
Development-only double mounting can reveal missing cleanup. Treat it as a useful signal, not as a React bug.
Using a singleton bus for request-specific SSR state
In SSR environments, prefer Context-based per-boundary bus creation to avoid leaking state between requests.
Using different imports for the same message class
Make sure the class registered by the registrator is the same class used by publishers and subscribers. Avoid duplicate barrels or inconsistent import paths that can lead to different runtime references.
Troubleshooting
"There is no registered event"
Check that:
the message is registered in a registrator;
the registrator instance was created;
up()was called before using the message;the message belongs to the scope where it is being used;
the same message class reference is used in registration and usage;
the component is not trying to use the bus during render before setup is complete;
the registrator and component use the same Postboy instance.
For more details, see Best Practices & Anti‑patterns.
Event handler runs twice in development
If you use React Strict Mode, effects may run twice during development through mount/unmount/remount checks.
Check that:
down()is called in effect cleanup;subscriptions are unsubscribed;
registration logic is safe for the expected lifecycle;
the duplicate behavior does not happen in production builds.
Feature messages still react after navigation
Check that:
the feature registrator is activated by the route/page component;
down()is called when the component unmounts;the message was not registered globally by mistake;
long-lived modules are not keeping independent subscriptions.
Callback never returns a value
The callback handler may receive the message but never complete it.
Check the general callback message documentation: Callback messages.
Component does not update after a message
Check that:
the subscription is active;
the message is fired after the subscription is created;
the state update uses React state setters;
the component is still mounted;
the update is not hidden by stale closures.
When updating state from subscriptions, prefer functional updates if the new value depends on the previous value:
Migration tips for React projects
From prop drilling
Use Postboy when communication crosses many component levels or when the sender should not know the receiver.
Do not replace simple parent-child props with a bus unnecessarily. Props are still the clearest option for local component relationships.
From Context-only communication
React Context is good for dependency access and relatively stable shared values.
Postboy is useful when you need explicit event-like communication, commands, or request/response flows between parts of the application.
A common pattern is to use Context to provide the Postboy bus, and Postboy messages to model communication.
From Redux, Zustand, MobX, or other stores
Postboy is not a state management library.
Keep dedicated stores for application state. Use Postboy for:
events;
commands;
side-effect orchestration;
cross-feature communication;
request/response flows;
decoupling modules that should not call each other directly.
From a simple EventEmitter
Replace untyped string events with typed message classes.
A simple migration path is:
event name → message class;
emit(...)→postboy.fire(...);on(...)→postboy.sub(...).subscribe(...);manual cleanup → subscriptions or registrator lifecycle.
Moving to RxJS 8
When your stack moves to RxJS 8, use the Postboy 4.x line.
Check:
RxJS operator changes in your application code;
dependencies that still require RxJS 7;
Postboy version alignment;
test setup and mocks that depend on RxJS behavior.
Summary
In React, Postboy works best when its lifecycle is aligned with component ownership:
Use a singleton bus for the simplest client-side setup.
Activate global registrators before rendering React when children subscribe immediately.
Use Context when you need isolated, replaceable, or SSR-safe bus instances.
Use
PostboyRootto combine Context-based bus creation and root registration.Use feature registrators for routes, dialogs, widgets, and temporary UI areas.
Render feature children only after their registrator is ready if they subscribe on mount.
Manage component subscriptions like normal RxJS subscriptions.
Do not publish, subscribe, or call callbacks during render.
Be aware of React Strict Mode and SSR boundaries.
Use the Postboy major version that matches your RxJS generation.
For message types, registration APIs, namespaces, middleware, and general architecture patterns, use the dedicated documentation sections linked at the top of this guide.