Skip to content

React Query & Devtools

Hooks for TanStack Query, plus browser and server devtools.

Import from @nice-code/action/react-query (peer dep: @tanstack/react-query).

import { useActionQuery, useActionMutation } from "@nice-code/action/react-query";
function UserProfile({ userId }: { userId: string }) {
const { data } = useActionQuery(
userDomain.action.getUser,
{ userId },
{ queryKey: ["user", userId] },
);
return <div>{data?.name}</div>;
}
function RenameUser() {
const { mutate } = useActionMutation(userDomain.action.updateName);
return <button onClick={() => mutate({ userId: "u_1", name: "Bob" })}>Rename</button>;
}

Input, output, and errors are all inferred from the action schema — no manual query function, no hand-written types.

A dockable in-app panel showing every action run: status, timing, input/output, routing, errors, and call stacks. Renders only when NODE_ENV === "development" (or with forceEnable). Import from @nice-code/action/devtools/browser.

import { ActionDevtoolsCore, NiceActionDevtools } from "@nice-code/action/devtools/browser";
const devtoolsCore = new ActionDevtoolsCore();
devtoolsCore.attachToDomain(appRoot);
function App() {
return (
<>
<MyApp />
<NiceActionDevtools core={devtoolsCore} position="dock-bottom" />
</>
);
}

Failed runs are classified by the same two axes you branch on in code (see Error Handling): an action error is labelled Expected Error (declared) vs Unexpected Error (undeclared / unhandled) from result.expected, and a wrapped foreign throw carries an unhandled badge from error.isUnhandled.

Logs action lifecycle (started / progress / success / error) with timings — pretty lines or newline-delimited JSON. Import from @nice-code/action/devtools/server.

import { ActionServerDevtools } from "@nice-code/action/devtools/server";
const devtools = new ActionServerDevtools({ format: "json", logPayloads: false });
devtools.attachToDomain(appRoot);

Identifies a runtime environment and routes actions to the right handler:

RuntimeCoordinate.env("backend"); // named env
RuntimeCoordinate.env("backend").specify({ perId: "worker-1" }); // env + instance
RuntimeCoordinate.env("backend").withPersistentId(id.toString()); // env + persistent instance id
RuntimeCoordinate.unknown; // unspecified

connectChannel / serveChannel are the supported entry points. For the rare routing that isn’t a single channel, the pieces they’re built on are exported — most under the @nice-code/action/advanced subpath:

  • acceptChannel / acceptChannelConnections — build a secure acceptor by hand with connection-aware cases.
  • createActionFetchHandler — the web-standard fetch handler on its own.
  • createInMemoryChannelPair / inMemoryCarrier — wire two runtimes in-process (tests, same-process peers) with no network.
  • createBinaryWireAdapter — the positional binary codec defineChannel builds for you, for custom carriers.

Reach for these only when a single channel doesn’t model your routing.