@revinel/react
v0.9.0
Published
Headless React hooks and tracking helpers for Revinel publisher integrations.
Maintainers
Readme
@revinel/react
React bindings for Revinel publisher integrations. Covers both integration jobs:
- Render ads on your site:
RevinelProvider+ hooks, on top of@revinel/sdk. - Acquire advertisers: the
<TierSelector>/<TierSelectorDialog>"Subscribe to advertise" widget.
Install
npm install @revinel/reactreact (>=18) is a peer dependency.
SSR note.
useAd/useAdsfetch on the client. That's fine for client-only apps, but it means a layout shift and no server-rendered ad. In an SSR framework (Next.js, Remix, TanStack Start), fetch on the server with@revinel/sdk'sgetAdand render the markup yourself; useuseTrackingon the client for impressions/clicks.
Render ads
Wrap your app once, then read ads with the hooks:
import { RevinelProvider, useAd, useTracking } from "@revinel/react"
const AdSlot = () => {
const { data: ad } = useAd({ weight: { gte: 2.5 } }) // premium placement
const { impressionRef, getClickProps } = useTracking(ad?.id)
if (!ad) return null // no eligible ad, render nothing
return (
<a ref={impressionRef} href={ad.websiteUrl} {...getClickProps()}>
{ad.name}
</a>
)
}
export const Ads = () => (
<RevinelProvider workspaceId="your-workspace-id">
<AdSlot />
</RevinelProvider>
)useTracking takes the ad id (or anything carrying one, like the full ad or your own render
shape), records a viewable impression (via IntersectionObserver), and wires click
tracking through getClickProps() (which also tracks middle-click / open-in-new-tab).
For a layout-persistent ad that never remounts on client-side navigation (a banner above the
router outlet), impressions only fire once per ad id. Pass resetKey set to something that
changes per page view (the current pathname) to restore one impression per page view:
useTracking(ad, { resetKey: pathname }).
Rendering multiple slots? Use one
useAds({ count: n }), notn × useAd(). EachuseAd()sends the same request, so the shared edge cache returns the same ad every time, so you'd render duplicates and double-count impressions.useAds({ count })returnsndistinct ads in one call; passexcludeIdsto dedupe against ads fetched elsewhere.
Type ad.meta once via @revinel/sdk's RevinelMetaRegistry (see its README) and every
hook is typed with no per-call generic. If impressionRef can't sit on your ad element
(e.g. a Slot/asChild wrapper that swallows refs), put it on a wrapping element or an
absolutely-positioned sentinel inside the ad.
Composing refs
Need the ad node yourself too (a ref forwarded from a parent, or Base UI/Radix
render/asChild)? Pass your external ref as ref and impressionRef composes it in, so
you still attach a single ref. In React 19 ref is just a prop, so no forwardRef:
function AdLink({ ref, ...props }: ComponentProps<"a">) {
const { data: ad } = useAd()
const { impressionRef, getClickProps } = useTracking(ad?.id, { ref })
if (!ad) return null
return <a ref={impressionRef} href={ad.websiteUrl} {...getClickProps()} {...props} />
}Object and callback refs both work, and cleanup is React 19-safe (a callback ref's returned
cleanup is honored; on React 18 the null detach is synthesized). A stable ref is churn-free;
a new inline callback ref each render reattaches (standard React).
Tier selector (acquire advertisers)
workspaceId (and optional appUrl) are inherited from RevinelProvider, so inside one
the widget takes no config props (theme defaults to "auto"):
import { TierSelector } from "@revinel/react"
;<TierSelector onCheckout={e => {}} />Or as a controlled modal:
import { TierSelectorDialog } from "@revinel/react"
const [open, setOpen] = useState(false)
;<TierSelectorDialog open={open} onClose={() => setOpen(false)} />Outside a provider, pass workspaceId (and appUrl for self-hosted) directly:
<TierSelector workspaceId="your-workspace-id" />.
Build your own pricing UI instead with useTiers() + useCheckout():
import { useCheckout, useTiers } from "@revinel/react"
const { data: tiers } = useTiers()
const { redirectToCheckout, isPending } = useCheckout()
// each tier carries structured features: tier.features is { label, type }[]
// redirectToCheckout({ tierPriceId }) creates the session and navigates to StripeExports
- Provider:
RevinelProvider,useRevinelClient,useRevinelConfig - Ads:
useAd,useAds,useTracking - Tiers/checkout:
useTiers,useCheckout - Widgets:
TierSelector,TierSelectorDialog
Every hook's option and return types are exported (RevinelAdOptions, RevinelQueryState, …)
so you can type wrappers around them.
API reference
Full reference and guides: revinel.com/docs. The OpenAPI
spec is served at /v1/openapi.json on the Revinel API.
