> ## 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.

# E-commerce store

> Track a shop from product view to purchase with the Analytics Web template events.

This page tracks a React shop with Analytics Web and its hooks from
`@grainql/analytics-web/react`. Every block uses Analytics Web. The template methods send the
events `add_to_cart`, `remove_from_cart`, `checkout`, `purchase`, and `search`. The dashboard
reads them by these names. Read [E-commerce tracking](/guides/ecommerce) for the funnel and
the rules.

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

export function App({ children }: { children: React.ReactNode }) {
  return <GrainProvider config={{ tenantId: 'your-tenant-id' }}>{children}</GrainProvider>;
}
```

## Product view

There is no template method for a product view. Use `track` with `useTrack`.

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

export function ProductPage({ product }: { product: Product }) {
  const track = useTrack();

  useEffect(() => {
    track('product_viewed', {
      product_id: product.id,
      product_name: product.name,
      category: product.category,
      price: product.price
    });
  }, [product.id, track]);

  return <ProductDetails product={product} />;
}
```

## Add to cart

`trackAddToCart` takes `itemId`, `itemName`, `price`, `quantity`, `currency`, `category`, and
`variant`. `useGrainAnalytics` returns the client that holds the template methods.

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

export function AddToCartButton({ product }: { product: Product }) {
  const grain = useGrainAnalytics();
  const { addItem } = useCart();

  const handleClick = () => {
    grain.trackAddToCart({
      itemId: product.id,
      itemName: product.name,
      price: product.price,
      quantity: 1,
      currency: 'USD',
      category: product.category,
      variant: product.variant
    });
    addItem(product);
  };

  return <button onClick={handleClick}>Add to Cart</button>;
}
```

## Remove from cart

`trackRemoveFromCart` takes the same properties as `trackAddToCart`.

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

export function CartItem({ item }: { item: CartLine }) {
  const grain = useGrainAnalytics();
  const { removeItem } = useCart();

  const handleRemove = () => {
    grain.trackRemoveFromCart({
      itemId: item.id,
      itemName: item.name,
      price: item.price,
      quantity: item.quantity,
      currency: 'USD'
    });
    removeItem(item.id);
  };

  return <button onClick={handleRemove}>Remove</button>;
}
```

## Checkout

Call `trackCheckout` when the checkout starts, with the cart as `items`. Each item holds `id`,
`name`, `price`, and `quantity`. If the payment fails, call it again with `success: false` and
the `errorMessage`.

```tsx theme={null}
import { useGrainAnalytics } from '@grainql/analytics-web/react';
import { useRouter } from 'next/navigation';
import { useCart } from './useCart';

export function CheckoutButton({ paymentMethod }: { paymentMethod: string }) {
  const grain = useGrainAnalytics();
  const router = useRouter();
  const { items, total, clearCart } = useCart();

  const handleCheckout = async () => {
    const order = await createOrder(items);

    grain.trackCheckout({
      orderId: order.id,
      total,
      currency: 'USD',
      items: items.map((item) => ({
        id: item.id,
        name: item.name,
        price: item.price,
        quantity: item.quantity
      })),
      paymentMethod
    });

    const payment = await processPayment(order.id, paymentMethod);

    if (!payment.ok) {
      grain.trackCheckout({
        orderId: order.id,
        total,
        currency: 'USD',
        paymentMethod,
        success: false,
        errorMessage: payment.error
      });
      return;
    }

    clearCart();
    router.push(`/orders/${order.id}`);
  };

  return <button onClick={handleCheckout}>Complete Purchase</button>;
}
```

`createOrder` and `processPayment` are functions of your shop.

## Purchase

Call `trackPurchase` on the order confirmation page, with `{ flush: true }`. The flush sends the
event before the visitor leaves the page. `trackPurchase` takes `orderId`, `total`, `currency`,
`items`, `paymentMethod`, `shippingMethod`, `tax`, `shipping`, `discount`, and `couponCode`.

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

export function OrderConfirmation({ order }: { order: Order }) {
  const grain = useGrainAnalytics();

  useEffect(() => {
    grain.trackPurchase(
      {
        orderId: order.id,
        total: order.total,
        currency: order.currency,
        items: order.items,
        paymentMethod: order.paymentMethod,
        shippingMethod: order.shippingMethod,
        tax: order.tax,
        shipping: order.shippingCost,
        discount: order.discount,
        couponCode: order.couponCode
      },
      { flush: true }
    );
  }, [order.id, grain]);

  return <p>Order {order.id} is confirmed.</p>;
}
```

The effect depends on `order.id`, so a re-render with the same order sends one `purchase`
event.

## Search

`trackSearch` takes `query`, `results`, `filters`, `sortBy`, `category`, and `success`. Call it
after the results arrive, so `results` holds the count.

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

export function ProductSearch() {
  const grain = useGrainAnalytics();
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<Product[]>([]);

  const handleSearch = async () => {
    const found = await searchProducts(query);
    setResults(found);

    grain.trackSearch({
      query,
      results: found.length,
      filters: { category: 'footwear' },
      sortBy: 'popularity',
      success: true
    });
  };

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        handleSearch();
      }}
    >
      <input type="search" value={query} onChange={(event) => setQuery(event.target.value)} />
      <button type="submit">Search</button>
      <ResultList products={results} />
    </form>
  );
}
```

The TypeScript types of `@grainql/analytics-web` win over this page when they disagree. Read
[Template events](/core/template-events).


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