@wirestate/react [monorepo] [docs]
React integration for Wirestate. Providers and hooks for injecting services and communicating through events, commands, and queries.
Installation
npm install @wirestate/core @wirestate/react reflect-metadataProviders
ContainerProvider
Root provider. Exposes the top-level container to the React tree. Pass either an existing container instance or managed createContainer(...) config.
When container is a prebuilt container instance, the provider uses it as-is and never disposes it. React provider lifecycle hooks still run for entries registered through Wirestate binding helpers.
import { createContainer, Container } from "@wirestate/core";
import { ContainerProvider } from "@wirestate/react";
import { CounterService, LoggerService } from "./services";
const container: Container = createContainer({
entries: [CounterService, LoggerService],
activate: [LoggerService],
});
export function Application() {
return (
<ContainerProvider container={container}>
<SomeComponent />
</ContainerProvider>
);
}When config is provided, ContainerProvider creates and owns the container. Managed containers activate all provided entries by default; pass activate: false to skip core eager activation, or pass an array to activate only specific entries.
import { ContainerProvider } from "@wirestate/react";
import { CounterService, LoggerService } from "./services";
export function Application() {
return (
<ContainerProvider config={{ entries: [CounterService, LoggerService] }}>
<SomeComponent />
</ContainerProvider>
);
}SubContainerProvider
Creates a child container scoped to a subtree. Use it under ContainerProvider when a branch needs its own service bindings or per-service seeds. Child containers activate all provided entries by default; pass activate: false or a token array to override that.
import { ReactNode } from "react";
import { SubContainerProvider } from "@wirestate/react";
import { CounterService, LoggerService } from "./services";
function CounterServicesProvider(props: { children?: ReactNode }) {
return (
<SubContainerProvider entries={[CounterService, LoggerService]}>
{props.children}
</SubContainerProvider>
);
}
export function CounterPage() {
return (
<CounterServicesProvider>
<CounterView />
</CounterServicesProvider>
);
}Props:
| Prop | Type | Description |
|---|---|---|
entries | InjectableEntries | Services or binding descriptors to add to the child container. |
seeds | SeedEntries | Per-service seeds, e.g. [[CounterService, { count: 10 }]]. Applied on mount. |
activate | boolean | Array<ServiceIdentifier> | true by default. Pass false or specific entry tokens to control activation. |
Provider lifecycle
@OnProvision and @OnDeprovision run when a React provider commits, unmounts, or replaces its container. They are useful for services that need UI-scoped setup separate from core @OnActivated / @OnDeactivation. Import them from @wirestate/core for services shared across React and Lit.
import { Injectable, OnDeprovision, OnProvision } from "@wirestate/core";
@Injectable()
export class LoggerService {
@OnProvision()
public onProvision(): void {
// provider committed
}
@OnDeprovision()
public onDeprovision(): void {
// provider removed or replaced
}
}Provider lifecycle services are resolved so their hooks can run, even when activate: false skips general eager activation. Services that inject WireScope also participate so scope.isDeprovisioned and scope.isInactive reflect provider ownership even without provider hooks. Managed providers deprovision before disposing their container; external containers are deprovisioned but remain owned by the caller.
Injection hooks
useInjection(token)
Resolves a value from the nearest container. Re-resolves when the container resets.
import { useInjection } from "@wirestate/react";
import { CounterService } from "./services";
function CounterView() {
const counter = useInjection(CounterService);
return <span>{counter.count}</span>;
}useOptionalInjection(token)
Same as useInjection but returns null if the token is not bound.
Event hooks
useEventEmitter()
Returns a function that emits an event into the current container's event bus.
const emit = useEventEmitter();
emit("RESET");
emit("ADD", { amount: 5 });useEvent(type, handler)
Subscribes a handler to a single event type for the lifetime of the component.
useEvent("RESET", (event) => {
console.log(event.type, event.payload);
});useEvents(types, handler)
Subscribes to multiple event types with a single handler.
useEvents(["RESET", "CLEAR"], (event) => {
console.log(event.type, event.payload);
});useEventsHandler(handler)
Subscribes to all events. The handler receives the event type and payload.
useEventsHandler((event) => {
logger.log(event.type, event.payload);
});Command hooks
useCommandExecutor()
Returns a function that dispatches a command and returns a descriptor for its task.
const executeCommand = useCommandExecutor();
async function handleClick() {
await executeCommand("LOGIN", { username: "alice" }).task;
}useOptionalCommandExecutor()
Same as useCommandExecutor but returns null if no handler is registered.
useCommandHandler(type, handler)
Registers a command handler from a component for the lifetime of the component.
useCommandHandler("SCROLL_TOP", () => {
window.scrollTo(0, 0);
});Query hooks
useQueryExecutor()
Returns a function that calls a synchronous query handler and returns its value directly.
const query = useQueryExecutor();
const items = query("GET_ITEMS");useAsyncQueryExecutor()
Returns a function that calls a query handler and resolves its return value as a promise. It accepts both synchronous and asynchronous handlers.
const queryAsync = useAsyncQueryExecutor();
const items = await queryAsync("GET_ITEMS");useOptionalQueryExecutor() / useOptionalAsyncQueryExecutor()
Return null when no handler is registered instead of throwing.
useQueryHandler(type, handler)
Registers a query handler from a component.
useQueryHandler("GET_SCROLL_POS", () => window.scrollY);Container hooks
useContainer()
Returns the nearest Container instance. Useful for advanced manual resolution.
useScope()
Returns the current WireScope linked to the nearest container.
Use scope.isInactive as the usual async guard in services; it becomes true after service disposal or provider deprovision.
Test utilities
import { withContainerProvider } from "@wirestate/react/test-utils";withContainerProvider(children, container?) wraps a React tree with a ContainerProvider for use in tests.
License
MIT