Skip to content

React Commands ​

A React component sends commands by injecting the active container's CommandBus, and registers handlers while the component is mounted with useOnCommand.

Register the Plugin ​

Commands use the active container's CommandBus, which exists only when CommandsPlugin is registered in your provider's config.plugins. See React Containers > Messaging.

Execute a Command ​

Inject the CommandBus with useInjection and call execute. It runs the active handler and returns its result as-is, including a Promise if the handler is async. It throws when no handler exists. Use executeAsync when callers should always receive a Promise.

tsx
import { CommandBus } from "@wirestate/core";
import { useInjection } from "@wirestate/react";

function AddItemButton({ item }: { item: CartItem }) {
  const commandBus = useInjection(CommandBus);

  return (
    <button
      onClick={() => {
        const itemCount: number = commandBus.execute("ADD_CART_ITEM", item);

        console.info("Cart item count:", itemCount);
      }}
    >
      Add item
    </button>
  );
}

The injected bus is stable while the active container is unchanged, so it is safe to use in a useCallback or effect dependency list.

Execute an Async Command ​

Use executeAsync when the handler may return a Promise, or when the component should always work in an async way. It always resolves to a Promise.

tsx
import { CommandBus } from "@wirestate/core";
import { useInjection } from "@wirestate/react";
import { useCallback, useState } from "react";

function LogoutButton() {
  const commandBus = useInjection(CommandBus);
  const [pending, setPending] = useState(false);

  const logout = useCallback(async () => {
    setPending(true);

    try {
      await commandBus.executeAsync("LOGOUT");
    } finally {
      setPending(false);
    }
  }, [commandBus]);

  return (
    <button disabled={pending} onClick={() => void logout()}>
      Log out
    </button>
  );
}

Execute Optional Commands ​

When a command handler may be absent in some containers, pass a literal { optional: true } so a missing handler returns undefined instead of throwing.

tsx
import { CommandBus } from "@wirestate/core";
import { useInjection } from "@wirestate/react";

function DevtoolsButtons() {
  const commandBus = useInjection(CommandBus);

  return (
    <>
      <button onClick={() => commandBus.execute("TOGGLE_DEVTOOLS", undefined, { optional: true })}>
        Toggle devtools
      </button>
      <button onClick={() => void commandBus.executeAsync("EXPORT_DEVTOOLS_TRACE", undefined, { optional: true })}>
        Export trace
      </button>
    </>
  );
}

If the CommandBus itself may be absent (no CommandsPlugin registered), resolve it optionally with useInjection(CommandBus, { optional: true }) and guard before calling.

Handle a Command ​

tsx
import { useOnCommand } from "@wirestate/react";
import { useState } from "react";

function SearchPanel() {
  const [open, setOpen] = useState(false);

  useOnCommand("OPEN_SEARCH", () => setOpen(true));

  return open ? <div>Search</div> : null;
}

Handlers unregister when the component unmounts, the command type changes, or the active container changes. The hook keeps the latest handler without re-registering. If several handlers use the same command type, the newest one handles the command.

API Reference ​

CommandBus, CommandDispatchOptions, useInjection, useOnCommand.