@mrt.98/mrtx
v1.0.4
Published
A lightweight GetX-inspired state management library for React
Maintainers
Readme
mrtx
A lightweight, GetX-inspired state management library for React.
Built on a simple controller + observer pattern — no boilerplate, no magic, just plain TypeScript classes.
Install
npm install @mrt.98/mrtxPeer dependency: React 17 or higher is required.
Core concepts
| Piece | What it does |
|---|---|
| MrtxController | Base class — extend it, add state as properties, call this.update() to re-render |
| MrtxBuilder | React component — creates or finds a controller and re-renders when update() is called |
| put / find / deleteInstance / isRegistered | Global DI container — register and retrieve controllers by string key |
Quick start
import { MrtxController, MrtxBuilder } from '@mrt.98/mrtx';
class CounterController extends MrtxController {
count = 0;
increment() {
this.count++;
this.update(); // triggers re-render
}
}
export function Counter() {
return (
<MrtxBuilder
id="CounterController"
init={() => new CounterController()}
builder={(ctrl) => (
<div>
<p>{ctrl.count}</p>
<button onClick={() => ctrl.increment()}>+1</button>
</div>
)}
/>
);
}MrtxController
Extend this class for every piece of state in your app.
class MyController extends MrtxController {
// Add state as plain properties
loading = false;
data: string[] = [];
// Override lifecycle hooks (optional)
onInit() {
// called once when the controller is first registered
}
onClose() {
// called when the controller is disposed
}
// Call this.update() after any state mutation
async load() {
this.loading = true;
this.update();
this.data = await fetchData();
this.loading = false;
this.update();
}
}Methods
| Method | Description |
|---|---|
| update() | Notifies all listeners to re-render |
| onInit() | Lifecycle hook — override to run code on init |
| onClose() | Lifecycle hook — override to run cleanup code |
| init() | Called internally on first registration |
| close() | Called internally on disposal |
MrtxBuilder
React component that connects a controller to the UI.
<MrtxBuilder
id="MyController"
init={() => new MyController()}
autoDispose={true}
builder={(ctrl) => <div>{ctrl.data}</div>}
/>Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| id | string | No | — | Key used to register/find the controller in the DI container |
| init | () => T | No | — | Factory function that creates the controller |
| builder | (ctrl: T) => JSX.Element | Yes | — | Render function — called on every update() |
| autoDispose | boolean | No | true | If true, disposes the controller when the component unmounts |
You must provide at least one of
initorid.
- Provide both to create and register a controller.
- Provide only
idto find an already-registered controller.
DI Container
put(key, instance, overwrite?, global?)
Register a controller instance.
import { put } from '@mrt.98/mrtx';
put('AuthController', new AuthController(), true, true);
// ^ ^
// overwrite global (never auto-disposed)find(key)
Retrieve a registered controller. Throws if the key is not found.
import { find } from '@mrt.98/mrtx';
const auth = find<AuthController>('AuthController');deleteInstance(key)
Dispose and remove a controller.
import { deleteInstance } from '@mrt.98/mrtx';
deleteInstance('AuthController');deleteAllInstances()
Dispose and remove all non-global controllers.
import { deleteAllInstances } from '@mrt.98/mrtx';
deleteAllInstances(); // useful on logout / route resetisRegistered(key)
Check if a controller is currently registered.
import { isRegistered } from '@mrt.98/mrtx';
if (isRegistered('AuthController')) { ... }updateAllControllers()
Call update() on every registered controller.
import { updateAllControllers } from '@mrt.98/mrtx';
updateAllControllers(); // useful for theme or locale changesUsage patterns
Pattern 1 — Local controller
Scoped to a single component. Disposed automatically on unmount.
<MrtxBuilder
id="CounterController"
init={() => new CounterController()}
// autoDispose={true} is the default
builder={(ctrl) => <p>{ctrl.count}</p>}
/>Pattern 2 — Shared controller
One controller instance, multiple components subscribing to it.
// Component A — creates the controller and keeps it alive
<MrtxBuilder
id="TodoController"
init={() => new TodoController()}
autoDispose={false}
builder={(ctrl) => <TodoInput ctrl={ctrl} />}
/>
// Component B — finds the same instance by id
<MrtxBuilder
id="TodoController"
builder={(ctrl: TodoController) => <TodoList ctrl={ctrl} />}
/>Pattern 3 — Global controller
Registered once at app startup, accessible everywhere, never disposed.
// main.tsx or App.tsx — register before any component mounts
put('ThemeController', new ThemeController(), true, /* global */ true);
// Any component anywhere in the tree
<MrtxBuilder
id="ThemeController"
builder={(ctrl: ThemeController) => (
<div data-theme={ctrl.theme}>
<button onClick={() => ctrl.toggle()}>Toggle theme</button>
</div>
)}
/>TypeScript
All APIs are fully typed. The MrtxBuilder component is generic — TypeScript will infer the controller type from init and pass it typed into builder.
class UserController extends MrtxController {
name = 'Mohammad';
}
<MrtxBuilder
id="UserController"
init={() => new UserController()}
builder={(ctrl) => <p>{ctrl.name}</p>}
// ^ ctrl is typed as UserController
/>License
MIT — Mohammad Al-Thneebat
