npm package: @mrt.98/mrtx
Description: A lightweight GetX-inspired state management library for React
Author: Mohammad Al-Thneebat
Latest version: 1.0.4

# 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

```sh
npm install @mrt.98/mrtx
```

> **Peer 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

```tsx
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.

```ts
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.

```tsx
<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 `init` or `id`.  
> - Provide both to create **and** register a controller.  
> - Provide only `id` to **find** an already-registered controller.

---

## DI Container

### `put(key, instance, overwrite?, global?)`

Register a controller instance.

```ts
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.

```ts
import { find } from '@mrt.98/mrtx';

const auth = find<AuthController>('AuthController');
```

### `deleteInstance(key)`

Dispose and remove a controller.

```ts
import { deleteInstance } from '@mrt.98/mrtx';

deleteInstance('AuthController');
```

### `deleteAllInstances()`

Dispose and remove all **non-global** controllers.

```ts
import { deleteAllInstances } from '@mrt.98/mrtx';

deleteAllInstances(); // useful on logout / route reset
```

### `isRegistered(key)`

Check if a controller is currently registered.

```ts
import { isRegistered } from '@mrt.98/mrtx';

if (isRegistered('AuthController')) { ... }
```

### `updateAllControllers()`

Call `update()` on every registered controller.

```ts
import { updateAllControllers } from '@mrt.98/mrtx';

updateAllControllers(); // useful for theme or locale changes
```

---

## Usage patterns

### Pattern 1 — Local controller

Scoped to a single component. Disposed automatically on unmount.

```tsx
<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.

```tsx
// 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.

```tsx
// 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`.

```tsx
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](https://github.com/mrt98)