Amba

React SDK

React hooks for amba — useAmba, useUser, useCollection, useFlag, useEntitlement, useVariant, useTrackOnMount. Companion to @layers/amba-web.

@layers/amba-react wraps @layers/amba-web in reactive hooks that re-render when SDK state changes. Same imperative surface is still available through useAmba() for one-off calls — the hooks just add the missing reactivity layer.

Works with React 18 and React 19 (server components are not supported — every hook is a client hook).

1. Install

pnpm add @layers/amba-react @layers/amba-web react

react is a peer dependency; the package will use whichever React version your app has installed. @layers/amba-web is the underlying SDK — the hooks are a thin reactive layer over it.

2. Configure before render

Configure the underlying @layers/amba-web SDK before rendering <AmbaProvider>. The provider does not configure the SDK itself — it just plugs the React tree into the SDK's state-change channel.

// src/main.tsx (Vite)
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Amba } from '@layers/amba-web';
import { AmbaProvider } from '@layers/amba-react';
import App from './App';
 
await Amba.configure({
  projectId: import.meta.env.VITE_AMBA_PROJECT_ID!,
  clientKey: import.meta.env.VITE_AMBA_CLIENT_KEY!,
});
 
ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <AmbaProvider>
      <App />
    </AmbaProvider>
  </React.StrictMode>,
);

For Next.js, do the configure call inside a client-side bootstrap component that mounts at the root and wraps children in <AmbaProvider>. Use NEXT_PUBLIC_AMBA_PROJECT_ID and NEXT_PUBLIC_AMBA_CLIENT_KEY in your .env.local.

3. First hook: useUser

import { useUser } from '@layers/amba-react';
 
function Header() {
  const { user, isAuthenticated, loading } = useUser();
 
  if (loading) return <Skeleton />;
  if (!isAuthenticated) return <button onClick={signIn}>Sign in</button>;
  return <span>{user?.display_name ?? user?.email}</span>;
}
 
async function signIn() {
  // Re-uses the underlying @layers/amba-web SDK; the hook re-renders
  // automatically once the session is established.
  const { Amba } = await import('@layers/amba-web');
  await Amba.auth.signInAnonymously();
}

4. First collection: useCollection

import { useCollection } from '@layers/amba-react';
 
type Todo = { id: string; title: string; done: boolean };
 
function TodoList() {
  const {
    data: todos,
    loading,
    error,
    refetch,
  } = useCollection<Todo>('todos', {
    order: [{ column: 'created_at', direction: 'desc' }],
    limit: 50,
  });
 
  if (loading) return <Spinner />;
  if (error) return <p>Failed to load todos: {error.message}</p>;
 
  return (
    <>
      <button onClick={refetch}>Refresh</button>
      <ul>
        {todos?.map((t) => (
          <li key={t.id}>{t.title}</li>
        ))}
      </ul>
    </>
  );
}

useCollection re-fetches on mount, on options change, and whenever the SDK auth state changes. response.next_cursor is exposed on the returned response for cursor pagination.

5. Flags and entitlements

import { useFlag, useVariant, useEntitlement } from '@layers/amba-react';
 
function NewOnboarding() {
  const flag = useFlag('new_onboarding'); // FlagAssignment | null
  const variant = useVariant('signup_copy'); // 'control' | 'short' | 'long' | null
  const isPro = useEntitlement('pro');
 
  // `useFlag(name)` returns the full assignment record (or null while loading
  // / when the flag is not assigned to this user). Read `.enabled` for the
  // boolean gate.
  if (!flag?.enabled) return <ClassicOnboarding />;
  return <NewFlow copy={variant ?? 'control'} pro={isPro} />;
}

useEntitlement defaults to false while loading — safe for paywall gating (no flash-of-pro-content).

6. Track on mount

For page-view tracking:

import { useTrackOnMount } from '@layers/amba-react';
 
export default function HomePage() {
  useTrackOnMount('page_viewed', { path: '/home' });
  return <main>...</main>;
}

The track call is fire-and-forget; failures are swallowed so a flaky network doesn't crash render.

All hooks at a glance

The 4.0 release exposes a hook for every namespace. Each useX hook returns a resource shape — { data, loading, error, refetch, … } plus mutation helpers where the namespace supports writes.

Core

HookReturnsNotes
useAmba()Raw Amba SDK handleRe-renders on auth state change.
useAuth(){ session, isAuthenticated, loading, signInWithEmail, signUpWithEmail, signInWithApple, signInWithGoogle, signOut, refresh }Single entry point for the auth surface.
useUser(){ user, isAuthenticated, loading, error, update, refetch }Auto-fetches auth.me() when authenticated.
useEvents(){ track }Stable callback that fires a tracked event.
useCollection(name, options){ data, loading, error, refetch, response }Re-fetches on options JSON change + auth change.
useFlag(name)FlagAssignment | nullRead .enabled for the boolean gate. null while loading or when unassigned.
useFlags(){ data, loading, error, refetch }Bulk flag fetch. NEW in 4.0 — useFlag is the convenience layer over this.
useVariant(name)string | nullMulti-variant flag assignment.
useEntitlement(name)booleanDefaults to false while loading.
useEntitlements(){ data, loading, error, refetch }Full list of entitlement rows.
useConfig(){ values, version, loading, error, refetch }Remote-config bundle.
usePush(){ register, unregister, subscribe, unsubscribe, tokens, loading }Push-token + topic-subscription helpers.
useStorage(){ upload, list, get, delete, download, url }Media uploads + CDN URL helpers.
useTrackOnMount(event, props)side-effect — no returnFires once on mount; never throws.
useDiagnostics(){ ping, loading, result }Wire-verify ping.

Social

HookReturnsNotes
useFriends(){ data, loading, error, refetch, sendRequest, acceptRequest, declineRequest, removeFriend, blockUser, unblockUser }removeFriend(userId) is NEW in 4.0.
useGroups(){ data, create, get, update, delete, members, join, leave, invite }
useMessaging(){ conversations, createConversation, sendMessage, listMessages, getMessage, markRead }conversations replaces 3.x getConversations.
useFeeds(feedKey?){ data, loading, error, refetch }Activity feed.
useReviews(targetType, targetId){ data, create, update, delete, loading, error }
useModeration(){ reportUser, reportContent, getReportStatus }

Lifecycle

HookReturnsNotes
useSessions(){ data, list, revoke, loading, error }App-session list + revoke. NEW in 4.0.
useSync(){ pushChanges, pullChanges }Offline change replay. NEW in 4.0.
useContent(channel?){ today, library, item, createItem, updateItem }Names match the 4.0 spec (no getX aliases).
useOnboarding(){ status, nextStep, skipStep, complete }
useDeepLinks(){ get, create }
useReferrals(){ getReferralCode, claimReferral, create }
useStreaks(){ data, qualify, refetch }

Gamification

HookReturnsNotes
useAchievements(){ data, progress, loading, error, refetch }
useChallenges(){ data, get, claim, progress }
useXp(){ balance, history, claim }
useLeaderboards(){ get, entries, myRank }
useLeagues(){ me, cohort, loading, error, refetch }NEW in 4.0 — tiered leaderboard cohorts.
useCurrencies(currencyKey){ balance, transactions, loading, error }
useInventory(){ items, get, purchase, consume }
useStores(){ list, purchaseOptions, purchase }

Escape hatch: raw SDK

When you need something the hooks don't cover yet (e.g. storage.upload, ai.anthropic.messages.create, push.register), use useAmba():

import { useAmba } from '@layers/amba-react';
 
function UploadAvatar() {
  const Amba = useAmba();
  async function onFileChange(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (!file) return;
    const asset = await Amba.storage.upload({ bucket: 'avatars', file });
    console.log(asset.url);
  }
  return <input type="file" onChange={onFileChange} />;
}

useAmba() is just Amba from @layers/amba-web plus subscription to the version counter — so the component re-renders when the SDK's auth state changes.

Server components

Server components run before any client SDK is configured. Don't import @layers/amba-react (or @layers/amba-web) from server-only modules.

Common pitfalls

  • Hook called outside <AmbaProvider> throws "hooks must be used inside <AmbaProvider>". Wrap the root.
  • Amba.configure() not awaited before render causes hooks to throw "amba SDK not configured". Either await it before the first render, or render a <SDKLoading /> boundary that flips when Amba.appUserId is no longer undefined.
  • Two copies of @layers/amba-webuseUser, useFlag, etc. all import the same singleton from @layers/amba-web. If pnpm why @layers/amba-web shows two copies, the hooks subscribe to one and your Amba.configure() call configures the other. Hoist with pnpm dedupe.

See also

On this page