Skip to content

React MobX

Use @wirestate/react for containers and hooks. Use @wirestate/react-mobx for MobX and mobx-react-lite re-exports.

Service

MobX decorators need makeObservable(this).

ts
import { Injectable } from "@wirestate/core";
import { Action, Observable, makeObservable } from "@wirestate/react-mobx";

@Injectable()
export class CounterService {
  @Observable()
  public count: number = 0;

  public constructor() {
    makeObservable(this);
  }

  @Action()
  public increment(): void {
    this.count += 1;
  }
}

Dependencies

ts
import { Inject, Injectable } from "@wirestate/core";
import { Action, Observable, makeObservable } from "@wirestate/react-mobx";

@Injectable()
export class LoggerService {
  public log(...args: Array<unknown>): void {
    console.log("[log]", ...args);
  }
}

@Injectable()
export class CounterService {
  @Observable()
  public count: number = 0;

  public constructor(@Inject(LoggerService) private readonly logger: LoggerService) {
    makeObservable(this);
  }

  @Action()
  public increment(): void {
    this.logger.log("increment", this.count + 1);
    this.count += 1;
  }
}

Provider

tsx
import { ContainerProvider } from "@wirestate/react";
import { useMemo } from "react";
import { CounterService, LoggerService } from "./services";

export function Application() {
  const config = useMemo(() => ({ entries: [CounterService, LoggerService] }), []);

  return (
    <ContainerProvider config={config}>
      <Counter />
    </ContainerProvider>
  );
}

Component

Wrap components that read observable state with observer.

tsx
import { useInjection } from "@wirestate/react";
import { observer } from "@wirestate/react-mobx";
import { CounterService } from "./CounterService";

export const Counter = observer(function Counter() {
  const counter = useInjection(CounterService);

  return <button onClick={() => counter.increment()}>Count: {counter.count}</button>;
});

Computed Values

ts
import { Injectable } from "@wirestate/core";
import { Computed } from "@wirestate/react-mobx";

@Injectable()
export class CounterSErvice {
  public count: number = 10;

  @Computed()
  public get isEven(): boolean {
    return this.count % 2 === 0;
  }
}

Messaging

Events, commands, and queries are the same as the Signals guide. Reactivity does not change the bus model.

ts
import { Inject, Injectable, OnCommand, OnQuery, WireScope } from "@wirestate/core";

@Injectable()
export class AuthService {
  public constructor(@Inject(WireScope) private readonly scope: WireScope) {}

  @OnCommand("LOGOUT")
  public async onLogout(): Promise<void> {
    await clearSession();
  }

  @OnQuery("CURRENT_USER")
  public onQueryCurrentUser(): User | null {
    return this.scope.queryOptionalData<User>("SESSION_USER");
  }
}

Seeds

Read seed data in @OnActivated. Mutate observables inside an action.

ts
import { Inject, Injectable, OnActivated, WireScope } from "@wirestate/core";
import { Action, Observable, makeObservable } from "@wirestate/react-mobx";

@Injectable()
export class CounterService {
  @Observable()
  public count: number = 0;

  public constructor(@Inject(WireScope) private readonly scope: WireScope) {
    makeObservable(this);
  }

  @OnActivated()
  public onActivated(): void {
    this.applySeed();
  }

  @Action()
  private applySeed(): void {
    const seed = this.scope.getSeed<{ initialCount?: number }>(CounterService);

    if (typeof seed?.initialCount === "number") {
      this.count = seed.initialCount;
    }
  }
}
tsx
import { useMemo } from "react";

const config = useMemo(() => ({
  entries: [CounterService],
  seeds: [[CounterService, { initialCount: 10 }]],
}), [])
tsx
<ContainerProvider
  config={config}
>
  <Application />
</ContainerProvider>