API Bus using Redux Listener

The basic usage of Redux is to update slices in the global store via actions. Centralized change messages flow efficiently to UI components through selectors.
Flows that don’t fit this principle—such as processes that don’t update slices or asynchronous operations—can be handled with listeners using createListenerMiddleware.

Because topics other than store changes are the focus, from a UI development perspective there’s no direct need to introduce ListenerMiddleware. However, to work within Redux’s unified .dispatch() framework, existing actions can be handled not only by reducers but also by listeners’ effect handlers to add asynchronous side effects.
You can also add actions that flow only to listeners, which can be observed in DevTools. By adding custom middleware, you can assert event traces in test cases.

Promise-based shared API lock

When you want to wait for multiple API calls to complete, you can use a Promise guard pattern like the following.
As is well known, awaiting a Promise during processing behaves such that if you retain a reference to it, it will return the desired value once the process completes.

let promise = null; // Global variable inside ESM

export async function api_guard() {
  if (!promise) {
    store.dispatch(apiCallStarted());
    promise = async_executor();
  }
  return promise;
}

This part is a custom implementation unrelated to Redux. Listeners do not provide a target to await.
The line store.dispatch(apiCallStarted()) is an example of sending an arbitrary action to listeners as needed. Even if multiple callers invoke this function, async_executor() will execute only once, and store.dispatch() will be called immediately before it.

If you want to add a corresponding store.dispatch(apiCallCompleted()), implement it at the end of async_executor(), which is the client body.

Purpose of an API bus

The reason for adding such a message bus is that the application’s lifecycle is longer than that of UI components.

Because APIs are part of the application’s state, they should be implemented independently of UI implementation files. This is obvious for applications that have page navigation.

The data store itself can be handled by Redux’s basic features. The process of setting up that data can leverage RTK Query, or in cases where it doesn’t fit well, a custom implementation like this will be necessary.

⁋ Apr 6, 2026↻ Apr 6, 2026
中馬崇尋
Chuma Takahiro