React integration
The @reachbell/react package wraps the core SDK in a provider and a pair of hooks so your components can read the current permission state, prompt the user, and identify or tag the subscriber without touching the global ReachBell object directly.
Install
npm install @reachbell/react @reachbell/sdk
@reachbell/react re-exports the SDK from @reachbell/sdk for direct access when you need it, but day to day you should rarely reach past the hooks.
Wrap your tree
Mount <ReachBellProvider> once at the top of your React tree — typically inside whichever component renders your application root.
import { ReachBellProvider } from '@reachbell/react';
export default function App({ children }) {
return (
<ReachBellProvider
apiKey="rb_live_yourkey"
autoPrompt
>
{children}
</ReachBellProvider>
);
}
Provider props
apiKey— required. Your project's API key.apiUrl— optional. Override the API base URL for self-hosted or regional deployments.autoPrompt— optional boolean. Arms a one-shot user-gesture listener that triggers the soft prompt on the first interaction.
StrictMode-safe. The provider holds a
useRefguard aroundReachBell.init()so React 18+ StrictMode's double-invoke dev pass does not re-init the SDK. The SDK itself is also idempotent on init, so the worst case in any edge condition is a no-op, never a duplicate service worker registration.
Drop the service worker at the root
@reachbell/react is a thin wrapper, not a replacement for the service worker file. You still need reachbell-sw.js served from your site's root:
https://yoursite.com/reachbell-sw.js
If your bundler outputs to dist/ or build/, copy the file into the static asset directory that ends up at the deployed root. For Next.js, see the dedicated Next.js guide.
useReachBell()
The primary hook. Returns the SDK's reactive state and a stable set of action callbacks.
import { useReachBell } from '@reachbell/react';
function SubscribeButton() {
const { isReady, permission, prompt } = useReachBell();
if (!isReady) return null;
if (permission === 'granted') return <span>You're subscribed.</span>;
if (permission === 'denied') return <span>Notifications blocked.</span>;
return (
<button
onClick={async () => {
const result = await prompt();
if (!result.ok) console.warn('prompt failed:', result.reason);
}}
>
Get notified
</button>
);
}
Returned shape
isReady—boolean. BecomestrueonceReachBell.init()has resolved successfully.permission—'default' | 'granted' | 'denied'. MirrorsNotification.permissionand re-renders when it changes.prompt()— wrapsReachBell.prompt(). Returns{ ok, reason }.identify(externalId)— wrapsReachBell.identify(). Returns{ ok, reason }.tag(tags)— wrapsReachBell.tag(). Pass a string array. Returns{ ok, reason }.sdk— the underlyingReachBellinstance. Escape hatch for anything the hook doesn't expose.
useNotificationPermission()
A lighter hook that only tracks the browser's permission state. Useful when a component cares about the permission but doesn't need to call any actions.
import { useNotificationPermission } from '@reachbell/react';
function PermissionBadge() {
const { permission, refresh } = useNotificationPermission();
return (
<div>
<span>Status: {permission}</span>
<button onClick={refresh}>Re-check</button>
</div>
);
}
refresh() re-reads Notification.permission synchronously. The hook also listens for the permissionchange event under the hood, so most apps never need to call refresh() manually — it's there for the rare case where the browser's event doesn't fire (some Safari versions).
Identify after login
The cleanest place to call identify() is in the effect that runs when your auth state transitions from anonymous to logged-in.
import { useEffect } from 'react';
import { useReachBell } from '@reachbell/react';
import { useCurrentUser } from './auth';
function IdentifyOnLogin() {
const { isReady, identify } = useReachBell();
const user = useCurrentUser();
useEffect(() => {
if (!isReady || !user) return;
identify(user.id).then((r) => {
if (!r.ok) console.warn('identify failed:', r.reason);
});
}, [isReady, user, identify]);
return null;
}
Render it once anywhere inside the provider — it has no UI of its own, it's just a side effect.
Tag based on plan
Same pattern for tags. The example below replaces the device's tag set whenever the user's plan changes.
function TagOnPlanChange() {
const { isReady, tag } = useReachBell();
const { plan } = useCurrentUser() ?? {};
useEffect(() => {
if (!isReady || !plan) return;
tag([plan]);
}, [isReady, plan, tag]);
return null;
}
Tag replaces, it doesn't append. Each
tag()call overwrites the device's tag set. If you want additive semantics, read the current tags from the dashboard or your own user store and merge before calling.
What's next?
- If you're on Next.js App Router, read the Next.js guide for the
"use client"provider pattern. - See the Subscribers API for the server-side counterpart to
identify()andtag(). - Build a re-engagement series with Automation.