ScaleBun
Skip to article

React

webDeveloper

Wire ScaleBun into a React app — an error boundary for render errors, a router-agnostic route hook, reactive feature flags, coachmark anchors, and in-app message slots.

Updated Reviewed

Install#

Terminal
npm i @scalebun/web @scalebun/web-react

Peer dependency: react >=17.

Initialize#

One component does the whole integration: it initializes the SDK once (client-side only), installs the render-error boundary, and provides the in-app messaging client.

src/main.tsxTSX
import { ScaleBunProvider } from '@scalebun/web-react';
<ScaleBunProvider  config={{ clientKey: 'skb_live_ck_…', apiBaseUrl: 'https://api.scalebun.com/api/v1' }}  routeName={location.pathname}  onReady={() => ScaleBun.identify(currentUserId)}>  <App /></ScaleBunProvider>;
PropDefaultWhat it does
configRequired. Passed to ScaleBun.init.
routeNameCurrent route, reported via trackScreen.
errorBoundarytruefalse keeps your own error handling.
fallback / onErrorForwarded to the boundary.
inApptruefalse if you supply your own <InAppProvider>.
inAppClient / inAppThemeBring your own client, or set the theme.
onReadyFires once after init resolves — the right place for identify().

Composing it yourself#

The provider is composition over parts that are all still exported, so the granular form keeps working if you want the pieces separately:

TSX
import ScaleBun, { ScaleBunErrorBoundary, useRouteName } from '@scalebun/web-react';
void ScaleBun.init({ clientKey, apiBaseUrl });   // module scope

Render errors#

React swallows render errors before window.onerror sees them, so the boundary is the only way to capture them.

TSX
import { ScaleBunErrorBoundary } from '@scalebun/web-react';
<ScaleBunErrorBoundary  fallback={(error) => <p>Something broke: {error.message}</p>}  onError={(error, info) => console.warn(info.componentStack)}>  <App /></ScaleBunErrorBoundary>;
PropTypeNotes
childrenReactNodeRequired.
fallbackReactNode | (error) => ReactNodeRendered after a caught error. Omitted renders nothing.
onError(error, info) => voidRuns after the error is captured.

The React componentStack is attached to the captured error automatically.

Route names#

useRouteName is router-agnostic — pass whatever your router exposes. It fires once per distinct value.

TSX
import { useLocation } from 'react-router-dom';import { useRouteName } from '@scalebun/web-react';
function App() {  useRouteName(useLocation().pathname);  return <Routes />;}

Prefer a matched pattern over a raw pathname where your router offers one: /orders/:id groups in the dashboard, /orders/8412 does not.

Feature flags#

useFlag subscribes the component to the flag and re-renders when the value changes — so a kill-switch flipped in the dashboard reaches the screen, not just the cache.

TSX
import { useFlag } from '@scalebun/web-react';
function Checkout() {  const variant = useFlag('checkout-cta', 'control');  return variant === 'treatment' ? <NewCta /> : <Cta />;}

The fallback is required and is what renders before the first config payload arrives, during SSR, and if the key is missing — so pass the value you would ship without the flag.

Reading the facade#

TSX
import { useScaleBun } from '@scalebun/web-react';
const scalebun = useScaleBun();scalebun.track('cart_viewed', { items: 3 });

Coachmarks#

Mark elements a tour can anchor to. data-scalebun-anchor is the most churn-resistant anchor strategy — it survives class and DOM changes that break a CSS selector.

TSX
import { useCoachmarks, ScaleBunAnchor, useScaleBunAnchor } from '@scalebun/web-react';
<ScaleBunAnchor name="checkout">  <button>Checkout</button></ScaleBunAnchor>;
// or spread the attribute onto your own element<button {...useScaleBunAnchor('checkout')}>Checkout</button>;
const coach = useCoachmarks();coach.startById('onboarding');

In-app messages#

Inline formats render in the page, so they need a slot to mount into.

TSX
import { InAppProvider, InAppSlot, useInApp } from '@scalebun/web-react';
<InAppProvider theme="auto">  <InAppSlot name="empty-state" /></InAppProvider>;

<InAppProvider> creates and owns one client; pass client to supply your own. For a custom element, useInAppSlot(name) returns a ref to attach. useInApp() throws outside a provider — that is deliberate, so a silently dead slot is not possible.

Waiting for the SDK#

Component code that reaches for a feature controller should await whenReady() — it resolves once init() has settled, succeeded or failed.

TSX
useEffect(() => {  let live = true;  void ScaleBun.whenReady().then(() => {    if (live) ScaleBun.inbox().mount({ trigger: bellRef.current! });  });  return () => { live = false; };}, []);

(The inbox is one of the opt-in features — enable it with features: { inbox: true } at init, or the controller is a no-op for a second reason.)

Without it, an accessor called before the runtime exists returns a no-op object — every method silently does nothing, forever. In a component tree that is the normal case rather than an edge case.

Next#

React · Integrations · Web SDK · ScaleBun