Skip to main content

Why React Hooks?

Grain’s React hooks eliminate boilerplate and provide a React-friendly API for analytics and remote configuration. Without hooks:
// Manual state management, effects, and cleanup
const [heroText, setHeroText] = useState('Loading...');

useEffect(() => {
  const fetchConfig = async () => {
    const value = await grain.getConfigAsync('hero_text');
    setHeroText(value);
  };
  
  const listener = (configs) => setHeroText(configs.hero_text);
  grain.addConfigChangeListener(listener);
  
  fetchConfig();
  
  return () => grain.removeConfigChangeListener(listener);
}, []);
With hooks:
const { value: heroText } = useConfig('hero_text');
Same functionality, much simpler.

Installation

Hooks are included in the main package:
npm install @grainql/analytics-web
React 16.8+ is required (peer dependency, not bundled).

Quick Start

Wrap your app with GrainProvider, then use hooks in components:
import { GrainProvider, useConfig, useTrack } from '@grainql/analytics-web/react';

function App() {
  return (
    <GrainProvider config={{ tenantId: 'your-tenant-id' }}>
      <HomePage />
    </GrainProvider>
  );
}

function HomePage() {
  const { value: heroText } = useConfig('hero_text');
  const track = useTrack();

  return (
    <div>
      <h1>{heroText || 'Welcome!'}</h1>
      <button onClick={() => track('cta_clicked')}>
        Get Started
      </button>
    </div>
  );
}

Available Hooks

useConfig

Get a single configuration value with cache-first loading:
const { value, isRefreshing, error, refresh } = useConfig('hero_text');
Automatically re-renders when configuration changes. Perfect for feature flags and dynamic content.

useAllConfigs

Get all configurations as an object:
const { configs, isRefreshing, error, refresh } = useAllConfigs();
Use when you need multiple config values or want to iterate over all configurations.

useTrack

Get a stable track function that doesn’t cause re-renders:
const track = useTrack();

// Safe to pass to child components
<Button onClick={() => track('button_clicked')} />
Why this matters: The function reference never changes, preventing unnecessary re-renders in child components.

useGrainAnalytics

Access the full Grain client for advanced operations:
const grain = useGrainAnalytics();

// Use any client method
grain.setUserId('user_123');
await grain.setProperty({ plan: 'premium' });
await grain.flush();
Use this when you need functionality beyond the specialized hooks.

The Provider

GrainProvider sets up Grain for your app. Two patterns available: Provider-managed (recommended):
<GrainProvider config={{ tenantId: 'your-tenant-id' }}>
  <App />
</GrainProvider>
External client (advanced):
const grain = createGrainAnalytics({ tenantId: 'your-tenant-id' });

<GrainProvider client={grain}>
  <App />
</GrainProvider>
Provider-managed is simpler for most cases. Use external client when you need to share the instance across multiple providers or need access outside React.

Key Benefits

Cache-First Loading: Hooks return cached values immediately, then fetch fresh data in the background. Your UI never waits for network requests. Automatic Updates: Components automatically re-render when configurations change. No manual listener management. Performance Optimized: Hooks use React’s built-in optimization (memo, useCallback) to prevent unnecessary re-renders. Type Safe: Full TypeScript support with type inference for all hooks and props. React Patterns: Follows React conventions with hooks, context, and functional patterns.

Context API Explanation

If you’re new to React’s Context API, here’s how it works with Grain: Context lets you share values across your component tree without passing props manually. GrainProvider creates a context with the Grain client:
// Provider at the top
<GrainProvider config={{ tenantId: 'your-tenant-id' }}>
  <App />          {/* Can use hooks */}
    <Page />       {/* Can use hooks */}
      <Component /> {/* Can use hooks */}
</GrainProvider>
Any component inside the provider can use Grain hooks, no matter how deeply nested. No prop drilling needed.

Common Patterns

Feature Flag

function App() {
  const { value: newUIEnabled } = useConfig('new_ui_enabled');
  
  return newUIEnabled === 'true' ? <NewUI /> : <LegacyUI />;
}

A/B Test

function Hero() {
  const { value: variant } = useConfig('hero_variant');
  const track = useTrack();
  
  useEffect(() => {
    track('hero_viewed', { variant });
  }, [variant, track]);
  
  return variant === 'B' ? <HeroB /> : <HeroA />;
}

User Authentication

function App() {
  const { user } = useAuth();
  const grain = useGrainAnalytics();
  
  useEffect(() => {
    if (user) {
      grain.identify(user.id);
      grain.setProperty({
        plan: user.plan,
        email: user.email
      });
    }
  }, [user, grain]);
  
  return <AppContent />;
}

Compared to Vanilla SDK

FeatureVanilla SDKReact Hooks
SetupManual instanceProvider + hooks
State managementManual useStateAutomatic
ListenersManual add/removeAutomatic
Re-rendersManual updatesAutomatic
CleanupManual in useEffectAutomatic
BoilerplateMore codeMinimal code
Both are powerful. Use hooks for React apps, vanilla SDK for other frameworks or environments.

Next Steps