useTracking
A declarative event map next to a component instead of track() calls sprinkled through handlers.
Imperative track() calls rot: they live inside handlers, get forgotten on the
new code path, and vanish in a refactor. useTracking moves the instrumentation
into one declarative map beside the component, so what a component reports is
readable in a single place.
import { defineTracking, useTracking } from '@voltro/client'
const spec = defineTracking('CheckoutButton', {
onMount: 'checkout.viewed',
onClick: (props) => ({ event: 'checkout.started', plan: props.plan }),
})
const CheckoutButton = (props) => {
const tracked = useTracking(spec, props, sink)
return <button {...tracked}>Checkout</button>
}useTracking(spec, props, sink) returns a copy of your props with every
callback the map names wrapped, so invoking onClick fires its event and then
calls your original handler. Spread the returned props; nothing is mutated.
In the map, onMount and onUnmount are lifecycle — they fire from an effect,
once per mount, and are never wrapped as props. Every other key is the name of a
callback prop to wrap. An entry is either a bare event name or a function of the
props returning { event, ...payload }.
The sink is yours to provide: (event: TrackingEvent) => void. The kernel is
transport-agnostic — it does not know where events go, which is also what makes
it testable with a recorder.
resolveTrackingEvent(entry, props) and wrapTrackedCallbacks(spec, props, sink)
are exported as pure functions if you need the behaviour outside a component.
For a typed, validated event taxonomy on top of the same sink, see the Analytics catalog.