Busser
A robust, opinionated, UI state flow management option for scalable and precise communication across ReactJS Components
Install / Use
/learn @codesplinta/BusserREADME
<img src="./logo.svg" width="0%" height="0%" alt="logo" style="padding:0;margin:0;">
A robust, opinionated, UI state flow management option for scalable and precise communication across ReactJS Components rendered on either the client-side or server-side. It heavily compliments react-query (@tanstack/react-query). busser is a synchronous state manager while react-query (@tanstack/react-query) is an asynchronous state manager. Just the same way RTK and RTK Query handle UI state and Server state respectively, busser and react-query (@tanstack/react-query) handle UI state and Server state respectively.
NOTE: This current version react-busser (v1.x.x) requires ReactJS (v16.8.x - 18.3.x) and React-Router v5.x.
Also, please take a look at this DEMO on codesandbox to see the power of react-busser.

Preamble
This library is made up of custom ReactJS hooks that provide basic tools to build stateful web applications that scale well even as the size of UI state and data flow paths grow or ranch out in very undeterministic manners. It makes it very easy to manage not just UI state but data flow across each React components that needs to access, share or recompute state in an effiecient manner. busser achieves this by using an event bus, eliminating wasteful re-renders where necessary by employing signals and utilizing the best places to store specific kinds of UI state.
There are 2 broad categories into which we can classify all of the state that any ReactJS app deals with
- Transient or Temporary state (e.g. UI state, derived state)
- Non-Transient or Permanent state (e.g. Server state, base state)
Read more about it here
So, why create busser ? Well, after using a lot of state managers like Redux, RTK Query, Zustand and Jotai. I found that the flow of data is very restricted/constrained because state (most state if not all state) was being stored in a single place (or slices of a single place - RTK Query, Zustand). I needed state to flow to where it was needed without gates or having to bypass or workaround predetermined/happy-path executionroutes. Also, most state managers out there are built in such a way that it encourages the storage and management of both UI state and server state be handled in the different parts of the same "machine" that are wierdly tightly coupled together while the business logic is fragmented across these different parts.
Therefore, busser takes a different approach by only handling UI state and leaving server state to be handled by something else like react-query (@tanstack/react-query).
There are 3 places that busser stores UI state:
- Storage (e.g. localStorage)
- URL
- Memory (e.g. JavaScript variables)
Storing state appropriately in these 3 places makes it really easy for data to flow unrestricted through your React frontend to get to all the client/server-rendered components it needs to go. I like to call busser: the all-in-one UI state manager. However, busser not only manages state but also manages the flow of state from one client/server-rendered component to another.
Additionally, busser is reactive alternative to the interactive model found in the way React works already. ReactJS makes use of imperative command-driven interactive APIs like setState(...) to drive UI updates. busser uses a different approach to this interactive model which is the reactive model (using an event bus to communicate shared data across multiple React. components).
When learning the Go programming language, in the section on Concurrency, there's this saying:
Do not communicate by sharing memory; instead, share memory by communicating
This is the basis of how busser works at its core, unlike Redux and Zustand that communicate by sharing memory (or sharing the store that contains the data to different ReactJS components), busser shares memory by communicating (or sharing the data via an event bus to different ReactJS components instead of the store).
Hooks that manage state in the URL
useSearchParamsState()useSearchParamStateValue()useSearchParamStateValueUpdate()
Hooks that manage the flow of state in the URL
useRoutingChanged()useRoutingBlocked()useRoutingMonitor()
------------------------------------------------------
Hooks that manage state in Storage
useBrowserStorage()useBrowserStorageWithEncryption()
------------------------------------------------------
Hooks that manage the flow of state in Storage
useBrowserStorageEvent()
------------------------------------------------------
Hooks that manage state in Memory
useList()useComposite()useCount()useProperty()usePromised()useSignalsList()useSignalsComposite()useSignalsCount()useSignalsProperty()
Hooks that manage flow of state in Memory
useBus()useOn()useSharedState()
------------------------------------------------------
Hooks specialized for specific tasks
useWindowSize()useLockBodyScroll()useGeoLocation()useControlKeysPress()useStateUpdatesWithHistory()useTextFilteredList()useTextSortedList()useTextFilteredSignalsList()useBroswserNetworkStatus()useBrowserScreenActivityStatusMonitor()useUICommands()
It's very important to note that busser exists only because of the inefficiencies present in the implementation of ReactJS. ReactJS claims to be wholly reactive but it really isn't because ReactJS is by definition a tree of interactive components, imperatively controlling and communicating with one another. This creates the restriction (i was speaking about earlier) for the pathways that data can travel in the web application. Libraries like MobX, pursue the wholly reactive option using Observables. However, busser borrows from MobX but unlike MobX, busser makes the component wholly reactive from the inside rather than from the outside.
Read more about this here.
Though busser tries to sidestep these inefficiencies to a large extent, it only goes so far in doing so. Finally, busser will have no reason to exist if these inefficiencies never existed in ReactJS in the first place.
Here is an introductory example of how to use the useBrowserStorage() and useUICommands() hooks
import React, { useRef } from "react";
import {
useBrowserStorage,
useUICommands
} from "react-busser";
export function App () {
const listRef = useRef<HTMLUListElement | null>(null);
const { getFromStorage } = useBrowserStorage({
storageType: "local" /* @HINT: makes use of `window.localStorage` */
});
const commands = useUICommands({
print: { /* @HINT: Print command options */
documentTitle: "My Printed List",
onBeforePrint: () => console.log("before printing...."),
onAfterPrint: () => console.log("after printing...."),
nowPrinting: () => console.log("currently printing...."),
}
});
const list = getFromStorage<string[]>("list", []);
return (
<>
<ul ref={listRef}>
{list.map((listItem) => {
return <li key={listItem} onClick={() => {
commands.hub.copy(
listItem
)
}}>{listItem}</li>
})}
</ul>
<button onClick={() => commands.hub.print(listRef)}>
Print List
</button>
</>
)
}
Here's another introductory example of how to use the useTextSortedList() hook
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import { SORT_ORDER, useTextSortedList, useIsFirstRender } from 'react-busser';
import './App.css';
type Article = {
title: string,
upvotes: number,
date: string
};
type NormalizedArticle = Omit<Article, "date"> & { date: Date }
function useSortedArticles (articles: Article[]) {
const normalizedArticles: NormalizedArticle[] = Array.isArray(articles) ? articles.map((article) => {
return {
...article,
/* @HINT: Turn date string into JavaScript Date object */
date: new Date(article.date)
}
}) : [];
const isFirstRender = use
