Both the model and actions type parameters default to void, so you can omit them entirely when neither is needed:
import { useActions, Lifecycle } from "chizu";
class Actions {
static Mount = Lifecycle.Mount();
}
// Bare call — M defaults to void
const actions = useActions<void, typeof Actions>();
actions.useAction(Actions.Mount, () => {
console.log("Mounted!");
});When a component needs to dispatch or listen to actions but doesn't manage any local state, pass void as the model type:
import { useActions, Action } from "chizu";
class Actions {
static Ping = Action("Ping");
}
const actions = useActions<void, typeof Actions>();
actions.useAction(Actions.Ping, () => {
console.log("Pinged!");
});You can also pass void for just the actions parameter while keeping a model:
const actions = useActions<Model, void>(initialModel);- Event forwarding – A component that listens to one action and dispatches another.
- Side-effects – Logging, analytics, or external API calls that don't need local state.
- Bridging – Connecting broadcast or multicast events to non-React systems (e.g. a WebSocket client).
- Coordination – Orchestrating other components via dispatch without storing data.
Lifecycle actions work exactly as they do with a regular model. Add lifecycle factories to your Actions class:
class Actions {
static Mount = Lifecycle.Mount();
static Unmount = Lifecycle.Unmount();
static Ping = Action("Ping");
}
const actions = useActions<void, typeof Actions>();
actions.useAction(Actions.Mount, () => {
console.log("Component mounted");
});
actions.useAction(Actions.Unmount, () => {
console.log("Component unmounting");
});If you need access to props or other external values, pass a data callback as the first argument. The third generic specifies the data shape:
function useTrackingActions(props: { userId: string }) {
const actions = useActions<void, typeof Actions, { userId: string }>(() => ({
userId: props.userId,
}));
actions.useAction(Actions.Track, async (context) => {
await fetch(`/api/track/${context.data.userId}`);
});
return actions;
}Void-model components can participate in broadcast and multicast communication. This is particularly useful for "listener-only" components:
import { useActions, Action, Distribution } from "chizu";
class BroadcastActions {
static UserLoggedIn = Action<string>("UserLoggedIn", Distribution.Broadcast);
}
class Actions {
static Broadcast = BroadcastActions;
}
export default function useAnalyticsActions() {
const actions = useActions<void, typeof Actions>();
actions.useAction(Actions.Broadcast.UserLoggedIn, (_context, username) => {
analytics.track("login", { username });
});
return actions;
}With a void model, context.actions.produce is still callable but there is nothing to mutate – TypeScript types the draft model as void, preventing property access. Similarly, actions.inspect returns an empty object since there are no fields to inspect.