Skip to content

React Signals

Use @wirestate/react for containers and hooks. Use @wirestate/react-signals for Preact Signals re-exports.

Service

ts
import { Injectable } from "@wirestate/core";
import { signal, Signal } from "@wirestate/react-signals";

@Injectable()
export class CounterService {
  public readonly count: Signal<number> = signal(0);

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

Dependencies

ts
import { Inject, Injectable } from "@wirestate/core";
import { signal, Signal } from "@wirestate/react-signals";

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

@Injectable()
export class CounterService {
  public readonly count: Signal<number> = signal(0);

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

  public increment(): void {
    this.logger.log("increment", this.count.value + 1);
    this.count.value += 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

Signals re-render React consumers when read during render.

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

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

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

Events

ts
import { Event, Inject, Injectable, OnEvent, WireScope } from "@wirestate/core";

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

  public increment(): void {
    this.scope.emitEvent("COUNT_INCREMENTED", 1);
  }
}

@Injectable()
export class AnalyticsService {
  @OnEvent("COUNT_INCREMENTED")
  public track(event: Event<number>): void {
    console.log("count changed", event.payload);
  }
}

Commands

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

@Injectable()
export class AuthService {
  @OnCommand("LOGOUT")
  public async onLogout(): Promise<void> {
    await clearSession();
  }
}
tsx
import { useCommandExecutor } from "@wirestate/react";

function LogoutButton() {
  const command = useCommandExecutor();

  return <button onClick={() => void command("LOGOUT").task}>Log out</button>;
}

Queries

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

@Injectable()
export class ThemeService {
  @OnQuery("CURRENT_THEME")
  public onQueryTheme(): string {
    return "dark";
  }
}
tsx
import { useQueryExecutor } from "@wirestate/react";

function ThemeButton() {
  const query = useQueryExecutor();

  return <button>Theme: {query<string>("CURRENT_THEME")}</button>;
}

Seeds

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

Read targeted seeds in @OnActivated.

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

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

  @OnActivated()
  public onActivated(): void {
    const seed = this.scope.getSeed<{ initialCount?: number }>(CounterService);

    if (typeof seed?.initialCount === "number") {
      this.count.value = seed.initialCount;
    }
  }
}