> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grainql.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React app

> Build a React app with the Analytics Web provider, hooks, a flag, a variant, and identified users.

This page builds one React app with Analytics Web and its hooks from
`@grainql/analytics-web/react`. Every block uses Analytics Web. For Grain Tag in React, with
heatmaps and no provider, read [React](/quickstart/react).

```bash theme={null}
npm install @grainql/analytics-web
```

## The provider

Wrap the app in `GrainProvider` one time, at the root. Pass `config` and the provider creates
the client. `defaultConfigurations` holds the value of each configuration key until the first
fetch completes. Every value is a string.

```tsx theme={null}
// App.tsx
import { GrainProvider } from '@grainql/analytics-web/react';
import { HomePage } from './HomePage';

export function App() {
  return (
    <GrainProvider
      config={{
        tenantId: 'your-tenant-id',
        defaultConfigurations: {
          hero_variant: 'A',
          new_dashboard_enabled: 'false'
        }
      }}
    >
      <HomePage />
    </GrainProvider>
  );
}
```

Analytics Web tracks page views by default (`enableAutoPageView`). Read
[GrainProvider](/react/grain-provider).

## Track an event

`useTrack` returns a stable function. It does not change between renders, so it is safe in
an effect dependency list and as a prop.

```tsx theme={null}
import { useTrack } from '@grainql/analytics-web/react';

export function SignupButton() {
  const track = useTrack();

  return (
    <button onClick={() => track('signup_clicked', { location: 'hero' })}>
      Get Started
    </button>
  );
}
```

Read [useTrack](/react/use-track).

## A feature flag

`useConfig` returns the cached value at once and refreshes in the background. The value is a
string, so compare it with `'true'`.

```tsx theme={null}
import { useConfig } from '@grainql/analytics-web/react';

export function Dashboard() {
  const { value } = useConfig('new_dashboard_enabled');

  return value === 'true' ? <NewDashboard /> : <LegacyDashboard />;
}
```

Read [Feature flags](/guides/feature-flags).

## An A/B variant

Read the variant with `useConfig`, show the content for that variant, and put the variant in
every event of the test. A filter on the `variant` property then separates the two groups.

```tsx theme={null}
import { useConfig, useTrack } from '@grainql/analytics-web/react';
import { useEffect } from 'react';

const variants = {
  A: { title: 'Welcome to Our App', buttonText: 'Get Started' },
  B: { title: 'Boost Your Productivity', buttonText: 'Start Free Trial' }
};

export function HeroSection() {
  const { value } = useConfig('hero_variant');
  const track = useTrack();
  const variant = value === 'B' ? 'B' : 'A';
  const content = variants[variant];

  useEffect(() => {
    track('hero_viewed', { variant });
  }, [variant, track]);

  return (
    <section>
      <h1>{content.title}</h1>
      <button onClick={() => track('hero_cta_clicked', { variant })}>
        {content.buttonText}
      </button>
    </section>
  );
}
```

Read [A/B tests](/guides/ab-testing).

## Identify on login

`useGrainAnalytics` returns the client. Call `identify` when a user logs in and `logout` when
the user logs out. `logout` returns the client to an anonymous ID.

```tsx theme={null}
import { useGrainAnalytics } from '@grainql/analytics-web/react';
import { useEffect } from 'react';
import { useAuth } from './useAuth';

export function AuthSync({ children }: { children: React.ReactNode }) {
  const grain = useGrainAnalytics();
  const { user } = useAuth();

  useEffect(() => {
    if (user) {
      grain.identify(user.id);
      grain.setProperty({ plan: user.plan });
    } else {
      grain.logout();
    }
  }, [user, grain]);

  return <>{children}</>;
}
```

`useAuth` is your own hook. Read [Identify users](/core/user-identification) and
[User properties](/core/user-properties).

## The complete app

```tsx theme={null}
// App.tsx
import { GrainProvider } from '@grainql/analytics-web/react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AuthProvider } from './AuthProvider';
import { AuthSync } from './AuthSync';
import { HeroSection } from './HeroSection';
import { Dashboard } from './Dashboard';

export function App() {
  return (
    <GrainProvider
      config={{
        tenantId: 'your-tenant-id',
        defaultConfigurations: {
          hero_variant: 'A',
          new_dashboard_enabled: 'false'
        }
      }}
    >
      <AuthProvider>
        <AuthSync>
          <BrowserRouter>
            <Routes>
              <Route path="/" element={<HeroSection />} />
              <Route path="/dashboard" element={<Dashboard />} />
            </Routes>
          </BrowserRouter>
        </AuthSync>
      </AuthProvider>
    </GrainProvider>
  );
}
```

`GrainProvider` sits outside `AuthProvider`, so `AuthSync` can read both contexts. React Router
navigates through the History API, so each route change is a page view.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.