# How the Pub/Sub State Management System Enables Real-Time Updates in MiniSearch

> Discover how MiniSearch uses the Pub/Sub state management system with create-pubsub for efficient real-time UI updates in React components. Learn about localStorage persistence and throttled AI stream handling.

- Repository: [Victor Nogueira/minisearch](https://github.com/felladrin/minisearch)
- Tags: internals
- Published: 2026-03-01

---

**MiniSearch implements a lightweight publish-subscribe (Pub/Sub) pattern using the `create-pubsub` library to synchronize mutable UI state across React components, with optional localStorage persistence and throttled updates for high-frequency AI streams.**

MiniSearch’s front-end relies on a centralized Pub/Sub state management system to handle real-time updates without the complexity of traditional context providers. Located in [`client/modules/pubSub.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/pubSub.ts), this architecture exposes a consistent three-function API—`set`, `listen`, and `get`—for every piece of mutable state, from search queries to streaming AI responses. By leveraging the third-party `create-pubsub` library, MiniSearch ensures that any component can subscribe to changes, read current values synchronously, or trigger updates that propagate instantly across the application.

## Centralized Pub/Sub Architecture in [`client/modules/pubSub.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/pubSub.ts)

All state instances reside in a single module, ensuring predictable access patterns and eliminating duplicate stores. Each domain-specific variable—such as `queryPubSub`, `responsePubSub`, or `textGenerationStatePubSub`—is created using the `createPubSub` factory function.

The factory returns a tuple of three functions:

```ts
const [set, listen, get] = createPubSub(initialValue);

```

- **`set`** – Updates the stored value and notifies all subscribers immediately.
- **`listen`** – Registers a callback that executes upon subscription and on every subsequent change.
- **`get`** – Returns the current value synchronously without triggering side effects.

This triplet pattern appears consistently across the codebase, from the search query state to the model loading progress indicator.

## Persistence with LocalStorage Integration

For state that must survive page reloads, MiniSearch extends the base Pub/Sub pattern with `createLocalStoragePubSub`. This wrapper, defined in [`client/modules/pubSub.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/pubSub.ts), synchronizes values to the browser's `localStorage` API.

The implementation retrieves existing stored values during initialization, parses them as JSON, and subscribes to its own Pub/Sub instance to write changes back to disk:

```ts
function createLocalStoragePubSub<T>(key: string, defaultValue: T) {
  const stored = localStorage.getItem(key);
  const pub = createPubSub(stored ? JSON.parse(stored) as T : defaultValue);
  const [, onChange] = pub;
  onChange(v => localStorage.setItem(key, JSON.stringify(v)));
  return pub;
}

```

Persisted state includes query suggestions, the last search token hash, user settings (such as dark mode preferences), and UI accordion menu states.

## Throttling High-Frequency Updates

AI-generated responses and internal reasoning streams can emit dozens of updates per second. To prevent React re-render thrashing, MiniSearch wraps high-frequency setters with `throttleit`, limiting updates to **12 times per second** (approximately 83ms intervals).

In [`client/modules/pubSub.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/pubSub.ts), the `responsePubSub` and its updater demonstrate this pattern:

```ts
export const responsePubSub = createPubSub("");
export const updateResponse = throttle(responsePubSub[0], 1000 / 12);
export const [, , getResponse] = responsePubSub;

```

The same throttling strategy applies to `reasoningContentPubSub`, ensuring that rapid token generation does not overwhelm the UI thread while maintaining the appearance of real-time streaming.

## Real-Time UI Subscriptions

React components consume Pub/Sub state through the `listen` function, establishing subscriptions that automatically clean up on unmount. This pattern eliminates prop drilling and ensures synchronized state across disconnected component trees.

For example, the chat log system subscribes to `textGenerationStatePubSub` to append log entries whenever the generation state transitions:

```ts
const [, listenToTextGenerationStateChanges] = textGenerationStatePubSub;
listenToTextGenerationStateChanges(state => {
  addLogEntry(`Text generation state changed to '${state}'`);
});

```

Similarly, search state listeners trigger side effects such as URL updates or analytics tracking whenever the underlying query changes.

## API Design and Usage Patterns

MiniSearch exposes a consistent naming convention across all Pub/Sub exports to simplify developer experience:

| Export Pattern | Purpose | Usage Example |
|--------------|---------|---------------|
| `updateX` / `setX` | State mutation | `updateResponse("new text")` |
| `getX` | Synchronous read | `const current = getResponse()` |
| `XPubSub` | Full tuple access | `const [, listen] = responsePubSub` |

Because all state resides in a **single module** ([`client/modules/pubSub.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/pubSub.ts)), any component can import the precise updater or getter it needs without instantiating duplicate stores. This centralized architecture guarantees that every subscriber receives the same latest data, fulfilling the requirements of a real-time update system.

## Summary

- MiniSearch implements a **Pub/Sub state management system** using the `create-pubsub` library, providing a lightweight alternative to context-heavy state solutions.
- All state lives in **[`client/modules/pubSub.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/pubSub.ts)**, exposing a consistent three-function API (`set`, `listen`, `get`) for every domain-specific variable.
- **Persistence** is achieved through `createLocalStoragePubSub`, which automatically syncs state to `localStorage` for settings, query history, and UI preferences.
- **Throttling** prevents UI thrashing during high-frequency updates (AI streaming), limiting renders to 12 times per second using `throttleit`.
- React components subscribe to changes via the `listen` function, enabling real-time synchronization across the application without prop drilling.

## Frequently Asked Questions

### How does MiniSearch persist state across page reloads?

MiniSearch uses a wrapper function called `createLocalStoragePubSub` defined in [`client/modules/pubSub.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/pubSub.ts). This function checks `localStorage` for an existing value on initialization, parses it if found, and sets up a subscription that writes JSON-serialized updates back to `localStorage` whenever the state changes. This pattern is used for settings, query suggestions, and UI accordion states.

### Why does MiniSearch throttle certain Pub/Sub updates?

High-frequency streams such as AI-generated responses and reasoning content can emit dozens of updates per second. Without throttling, React would attempt to re-render components at the same frequency, causing performance degradation and UI jank. MiniSearch wraps these setters with `throttleit` to limit updates to 12 times per second (approximately 83ms intervals), maintaining the appearance of real-time streaming while preserving frame rates.

### How do React components subscribe to Pub/Sub state changes?

Components import the `listen` function (the second element of the Pub/Sub tuple) from [`client/modules/pubSub.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/pubSub.ts). They invoke this function within a `useEffect` hook, passing a callback that updates local React state. The `listen` function returns an unsubscribe function that components call during cleanup to prevent memory leaks. This pattern appears throughout the codebase, from chat log components listening to text generation state changes to search interfaces tracking query updates.

### What is the difference between `set`, `listen`, and `get` in MiniSearch's Pub/Sub system?

These three functions form the core API of every Pub/Sub instance created by `createPubSub`. **`set`** (often exported as `updateX`) writes a new value and immediately notifies all subscribers. **`listen`** registers a callback that executes whenever the value changes, returning an unsubscribe function. **`get`** returns the current value synchronously without triggering side effects or subscriptions. This triplet pattern ensures predictable state management across the MiniSearch frontend.