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({
  apiKey: import.meta.env.VITE_AMBA_API_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_API_KEY=amb_dev_ck_XXXX.

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 enabled = useFlag('new_onboarding');
  const variant = useVariant('signup_copy'); // 'control' | 'short' | 'long' | null
  const isPro = useEntitlement('pro');
 
  if (!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

HookReturnsNotes
useAmba()Raw Amba SDK handleRe-renders on auth state change.
useUser(){ user, isAuthenticated, loading }Auto-fetches auth.me() when authenticated.
useCollection(name, options){ data, loading, error, refetch, response }Re-fetches on options JSON change + auth change.
useFlag(name)booleanOne flag fetch per mount per unique name.
useVariant(name)string | nullMulti-variant flag assignment.
useEntitlement(name)booleanDefaults to false while loading.
useTrackOnMount(event, props)side-effect — no returnFires once on mount; never throws.

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