Skip to main content

Managers and Middleware

Reactive Data Client uses the flux store pattern, which is characterized by an easy to understand and debug the store's undirectional data flow. State updates are performed by a reducer function.

Manager flux flowManager flux flow

In flux architectures, it is critical all functions in the flux loop are pure. Managers provide centralized orchestration of side effects. In other words, they are the means to interface with the world outside Data Client.

For instance, NetworkManager orchestrates data fetching and SubscriptionManager keeps track of which resources are subscribed with useLive or useSubscription. By centralizing control, NetworkManager automatically deduplicates fetches, and SubscriptionManager will keep only actively rendered resources updated.

This makes Managers the best way to integrate additional side-effects like metrics and monitoring. They can also be customized to change core behaviors.

Default managers
NetworkManagerTurns fetch dispatches into network calls
SubscriptionManagerHandles polling subscriptions
DevToolsManagerEnables debugging
Extra managers
LogoutManagerHandles HTTP 401 (or other logout conditions)

Examples

Reactive Data Client improves type-safety and ergonomics by performing dispatches and store access with its Controller

Middleware logging

import type { Manager, Middleware } from '@data-client/core';

export default class LoggingManager implements Manager {
getMiddleware = (): Middleware => controller => next => async action => {
console.log('before', action, controller.getState());
await next(action);
console.log('after', action, controller.getState());
};

cleanup() {}
}

Middleware data stream (push-based)

Adding a manager to process data pushed from the server by websockets or Server Sent Events ensures we can maintain fresh data when the data updates are independent of user action. For example, a trading app's price, or a real-time collaborative editor.

import type { Manager, Middleware } from '@data-client/core';
import type { EndpointInterface } from '@data-client/endpoint';

export default class StreamManager implements Manager {
protected declare middleware: Middleware;
protected declare evtSource: WebSocket | EventSource;
protected declare endpoints: Record<string, EndpointInterface>;

constructor(
evtSource: WebSocket | EventSource,
endpoints: Record<string, EndpointInterface>,
) {
this.evtSource = evtSource;
this.endpoints = endpoints;

this.middleware = controller => {
this.evtSource.onmessage = event => {
try {
const msg = JSON.parse(event.data);
if (msg.type in this.endpoints)
controller.setResponse(
this.endpoints[msg.type],
...msg.args,
msg.data,
);
} catch (e) {
console.error('Failed to handle message');
console.error(e);
}
};
return next => async action => next(action);
};
}

cleanup() {
this.evtSource.close();
}

getMiddleware() {
return this.middleware;
}
}

Controller.setResponse() updates the Reactive Data Client store with event.data.

Coin App

More Demos