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.
Basic Usage
Get a stable function for tracking events:
import { useTrack } from '@grainql/analytics-web/react';
function Button() {
const track = useTrack();
return (
<button onClick={() => track('button_clicked', { button: 'signup' })}>
Sign Up
</button>
);
}
Simple, clean, and efficient.
Why useTrack?
In React, functions created in components get recreated every render. This causes problems:
// ❌ Without useTrack: New function every render
function Component() {
const handleClick = () => {
grain.track('clicked');
};
return <ChildComponent onClick={handleClick} />;
// Child re-renders unnecessarily because handleClick changes
}
// ✅ With useTrack: Stable function reference
function Component() {
const track = useTrack();
const handleClick = useCallback(() => {
track('clicked');
}, [track]); // track never changes
return <ChildComponent onClick={handleClick} />;
}
useTrack returns a stable function reference that never changes, preventing unnecessary re-renders.
The useCallback Explanation
In React, useCallback memoizes functions so they don’t get recreated every render:
// New function every render
const handler = () => { /* ... */ };
// Same function across renders (unless dependencies change)
const handler = useCallback(() => { /* ... */ }, [dependencies]);
useTrack already uses useCallback internally, so you get a stable track function automatically.
Tracking Events
Track with event name and properties:
function Component() {
const track = useTrack();
const handlePurchase = () => {
track('purchase_completed', {
product_id: 'abc123',
price: 99.99,
currency: 'USD'
});
};
return <button onClick={handlePurchase}>Buy Now</button>;
}
Flush Option
Force immediate sending for critical events:
function Checkout() {
const track = useTrack();
const handleComplete = async () => {
await track('checkout_completed', {
order_id: 'order_123'
}, { flush: true });
// Event sent before navigation
router.push('/success');
};
return <button onClick={handleComplete}>Complete</button>;
}
Passing to Children
Safe to pass down to child components:
function Parent() {
const track = useTrack();
return (
<>
<Child onAction={() => track('action_1')} />
<Child onAction={() => track('action_2')} />
<Child onAction={() => track('action_3')} />
</>
);
}
Since track is stable, child components won’t re-render unnecessarily.
Common Patterns
function SignupButton() {
const track = useTrack();
return (
<button onClick={() => track('signup_clicked', {
location: 'header',
page: window.location.pathname
})}>
Sign Up
</button>
);
}
function ContactForm() {
const track = useTrack();
const handleSubmit = async (e) => {
e.preventDefault();
await track('form_submitted', {
form_type: 'contact',
fields: 4
}, { flush: true });
// Submit form
};
return <form onSubmit={handleSubmit}>...</form>;
}
Page View
function Page() {
const track = useTrack();
useEffect(() => {
track('page_viewed', {
page: '/dashboard',
title: 'Dashboard'
});
}, [track]);
return <div>...</div>;
}
Since track is stable, this effect only runs once when the component mounts.
Feature Usage
function ExportButton() {
const track = useTrack();
const handleExport = () => {
track('export_clicked', {
format: 'csv',
rows: 1000
});
// Perform export
exportData();
};
return <button onClick={handleExport}>Export</button>;
}
With Template Events
Access template methods through the client:
import { useGrainAnalytics } from '@grainql/analytics-web/react';
function LoginForm() {
const grain = useGrainAnalytics();
const handleLogin = async (email, password) => {
const success = await authenticate(email, password);
await grain.trackLogin({
method: 'email',
success
});
};
return <form>...</form>;
}
Or use useTrack for custom events:
function LoginForm() {
const track = useTrack();
const handleLogin = async (email, password) => {
const success = await authenticate(email, password);
await track('login', {
method: 'email',
success
});
};
return <form>...</form>;
}
The hook is already optimized, but here are tips for best results:
1. Don’t create track calls in render:
// ❌ Bad: Track call in render
function Component() {
const track = useTrack();
track('rendered'); // Tracks on every render!
return <div>...</div>;
}
// ✅ Good: Track in event handler or effect
function Component() {
const track = useTrack();
useEffect(() => {
track('mounted'); // Tracks once on mount
}, [track]);
return <div>...</div>;
}
2. Combine with useCallback for complex handlers:
function Component() {
const track = useTrack();
const handleComplexAction = useCallback((data) => {
// Complex logic
track('action', data);
}, [track]);
return <Child onAction={handleComplexAction} />;
}
Next Steps
Event Tracking
Learn about event tracking
useGrainAnalytics
Access full client API
Template Events
Use pre-built event methods