Skip to main content
Using Next.js? Check out the Next.js Quick Start for App Router and Pages Router examples.

Install the Package

npm
npm install @grainql/analytics-web
yarn
yarn add @grainql/analytics-web
pnpm
pnpm add @grainql/analytics-web

Wrap Your App

Add the GrainProvider at the top of your component tree:
import { GrainProvider } from '@grainql/analytics-web/react';

function App() {
  return (
    <GrainProvider config={{ tenantId: 'your-tenant-id' }}>
      <YourApp />
    </GrainProvider>
  );
}
Replace 'your-tenant-id' with the alias from your dashboard (not the UUID).
Pro tip: Store your tenant ID in an environment variable like VITE_GRAIN_TENANT_ID or REACT_APP_GRAIN_TENANT_ID.

Track Events

Use the useTrack hook anywhere in your app:
import { useTrack } from '@grainql/analytics-web/react';

function SignupButton() {
  const track = useTrack();
  
  const handleClick = () => {
    track('signup_clicked', {
      location: 'hero',
      button_text: 'Get Started'
    });
    
    // Your signup logic here
  };
  
  return <button onClick={handleClick}>Get Started</button>;
}
Events are automatically batched and sent every few seconds. No manual flushing needed.

Use Remote Config

Access configuration values with useConfig:
import { useConfig } from '@grainql/analytics-web/react';

function Hero() {
  const { value: heroText, loading } = useConfig('hero_text');
  
  return (
    <div>
      <h1>{heroText || 'Welcome!'}</h1>
      {loading && <span>Loading fresh content...</span>}
    </div>
  );
}
The component automatically re-renders when config updates. Default values load instantly from cache or the defaultConfigurations you provided.
How it works: Config uses a cache-first strategy. Your UI loads instantly with cached values, while fresh data loads in the background.

Set User Identity

Identify users to track them across sessions:
import { useGrainAnalytics } from '@grainql/analytics-web/react';

function LoginForm() {
  const { identify } = useGrainAnalytics();
  
  const handleLogin = async (email, password) => {
    const user = await yourLoginFunction(email, password);
    
    // Associate all future events with this user
    identify(user.id, {
      email: user.email,
      name: user.name,
      plan: user.plan
    });
  };
  
  return <form onSubmit={handleLogin}>...</form>;
}

Complete Example

Here’s a small app that puts it all together:
import { 
  GrainProvider, 
  useTrack, 
  useConfig,
  useGrainAnalytics 
} from '@grainql/analytics-web/react';

// 1. Wrap your app
function App() {
  return (
    <GrainProvider 
      config={{ 
        tenantId: 'your-tenant-id',
        defaultConfigurations: {
          hero_text: 'Welcome to Our App',
          feature_enabled: 'false'
        }
      }}
    >
      <HomePage />
    </GrainProvider>
  );
}

// 2. Use hooks in components
function HomePage() {
  const track = useTrack();
  const { value: heroText } = useConfig('hero_text');
  const { value: featureEnabled } = useConfig('feature_enabled');
  const { identify } = useGrainAnalytics();
  
  const handleSignup = (userId: string) => {
    // Identify the user
    identify(userId, { signup_date: new Date().toISOString() });
    
    // Track the event
    track('signup_completed', {
      method: 'email'
    });
  };
  
  return (
    <div>
      <h1>{heroText}</h1>
      
      {featureEnabled === 'true' && (
        <NewFeature />
      )}
      
      <button onClick={() => track('cta_clicked', { location: 'hero' })}>
        Get Started
      </button>
    </div>
  );
}

Track Page Views

For single-page apps, track route changes manually:
import { useEffect } from 'react';
import { useTrack } from '@grainql/analytics-web/react';
import { useLocation } from 'react-router-dom'; // or your router

function PageViewTracker() {
  const track = useTrack();
  const location = useLocation();
  
  useEffect(() => {
    track('page_viewed', {
      page: location.pathname,
      title: document.title
    });
  }, [location.pathname, track]);
  
  return null; // This component just tracks
}

// Add to your app
function App() {
  return (
    <GrainProvider config={{ tenantId: 'your-tenant-id' }}>
      <Router>
        <PageViewTracker />
        <Routes>...</Routes>
      </Router>
    </GrainProvider>
  );
}

What’s Next?

TypeScript users: All hooks are fully typed. You’ll get autocomplete and type checking out of the box.