Skip to content

vskstudio/takt-react

Repository files navigation

@vskstudio/takt-react

Idiomatic React wrapper for Takt privacy-friendly analytics.

npm version react 18 | 19 license


A thin, SSR-safe React layer over @vskstudio/takt-core. It never changes the wire payload or the privacy guarantees — it just makes Takt feel native in a React app.

  • <Takt> component — drop it once near the root; it boots analytics in a mount effect and provides the instance to the tree.
  • useTakt() hook — grab the live instance anywhere; returns a never-throwing no-op before mount or during SSR.
  • useTaktEvent() hook & <TaktEvent> component — declarative click tracking.
  • <takt-analytics> custom element — framework-agnostic, React-free embed for non-React pages.

Install

pnpm add @vskstudio/takt-react @vskstudio/takt-core

react (^18 || ^19) and @vskstudio/takt-core are peer dependencies.

Quick start — provider + hook

Mount <Takt> once near your root. It fires an initial pageview, wires SPA navigation, and provides the instance to every descendant:

import { Takt } from '@vskstudio/takt-react'

export function App() {
  return (
    <Takt domain="example.com" outbound files={['pdf', 'zip']}>
      <Routes />
    </Takt>
  )
}

Then track custom events from any descendant:

import { useTakt } from '@vskstudio/takt-react'

export function SignupButton() {
  const takt = useTakt()
  return (
    <button
      onClick={() =>
        takt.track('Signup', {
          props: { plan: 'pro' },
          revenue: { amount: '29.00', currency: 'EUR' },
        })
      }
    >
      Sign up
    </button>
  )
}

useTakt() always returns a usable instance: before <Takt> mounts (or during SSR) it hands back a never-throwing no-op, so your handlers never crash.

<Takt> props

Prop Type Default Description
domain string location.hostname Site identifier sent with every event.
endpoint string /api/event Ingestion endpoint.
scriptOrigin string First-party origin to derive the endpoint from ({origin}/api/event) — your Takt domain or a custom domain to dodge ad-blockers (endpoint wins over it).
outbound boolean false Auto-track outbound link clicks.
files boolean | string[] false Auto-track file downloads; pass extensions to restrict.
spa boolean true Track SPA navigations (pushState/replaceState + popstate).
track404 boolean false Report a 404 event on error pages ([data-takt-404] / <meta name="takt:404"> marker, or a 404 HTTP status).
respectDnt boolean true Suppress events when the browser's Do Not Track is enabled.
excludeLocalhost boolean true Suppress events on localhost and private IP ranges.
enabled boolean true Master on/off switch — set to false to disable all tracking at runtime.
sampleRate number 1 Fraction of sessions to track (0–1).
trackQuery boolean false Include the URL query string in pageview paths.
queryParams string[] Query parameters to preserve when trackQuery is true; omit to keep all.
scrubUrl (url: string) => string Transform the URL before it is sent. Component prop only — cannot be set as a custom-element attribute. Must be a developer-controlled function; never build it from user input.
tagged boolean false Auto-track [data-takt-tag] clicks.

Config props are read once when <Takt> mounts. Changing them afterwards has no effect — remount the component to reconfigure.

Declarative click tracking

Two equivalent ways to track a click without writing a handler.

useTaktEvent() returns an { onClick } you spread onto any element:

import { useTaktEvent } from '@vskstudio/takt-react'

export function BuyButton() {
  const onBuy = useTaktEvent({ name: 'Buy', revenue: { amount: '9.00', currency: 'EUR' } })
  return <button {...onBuy}>Buy</button>
}

<TaktEvent> wraps a single child and composes its existing onClick:

import { TaktEvent } from '@vskstudio/takt-react'

export function SignupCta({ onClick }: { onClick: () => void }) {
  return (
    <TaktEvent name="Signup" props={{ plan: 'pro' }}>
      <button onClick={onClick}>Sign up</button>
    </TaktEvent>
  )
}

Both resolve the active instance at click time, so they work inside <Takt> or with an init()-driven core setup, falling back to core's default instance otherwise.

Custom element (React-free)

For non-React pages, import the side-effecting ./element entry to register <takt-analytics>. It bundles core and pulls in no React runtime:

import '@vskstudio/takt-react/element'
<takt-analytics domain="example.com" outbound files></takt-analytics>

Privacy attributes (respect-dnt, exclude-localhost, spa) are on by default and only disabled by an explicit "false"/"0". Presence flags (outbound, files) activate when the attribute is present.

Widgets

Thin wrappers over Takt's server-rendered widgets. <TaktBadge> renders an <img> (the badge SVG); <TaktEmbed> renders an <iframe> (the embed dashboard). Both forward standard element attributes (className, style, …).

import { TaktBadge, TaktEmbed } from '@vskstudio/takt-react'

export function Footer() {
  return (
    <>
      <TaktBadge domain="example.com" variant="d" glyph="dash" />
      <TaktEmbed domain="example.com" theme="dark" />
    </>
  )
}

The badge alt defaults to "takt" but is overridable. The embed <iframe> is hardened: it ships sandbox="allow-scripts allow-same-origin" and a fixed referrerPolicy="strict-origin-when-cross-origin", both applied after your props so a consumer cannot weaken them. The optional host prop must be an absolute http(s) URL (validated by core, which reduces it to its origin); src is wrapper-controlled and cannot be overridden.

For dashboards you build yourself, createStats is re-exported from core:

import { createStats } from '@vskstudio/takt-react'

const stats = createStats({ domain: 'example.com' })
const summary = await stats.summary({ period: '7d' })

SSR / Next.js

The main entry ships with a built-in 'use client' banner, and <Takt> boots inside a mount effect, so nothing touches window/document on the server. In the Next.js App Router, render <Takt> from a client component (or the App Router root) and the rest of the API works unchanged. Importing @vskstudio/takt-react/element on the server is a no-op — registration is guarded behind a customElements check.

Privacy

All privacy behavior lives in @vskstudio/takt-core: Do Not Track support, localhost exclusion, opt-in/opt-out consent, and a frozen wire payload. This wrapper never alters any of it.

License

MIT