Thisbefine
Examples

React SPA (Vite/CRA)

For the Vite and Create React App folks

React SPA Example

Not using Next.js? No problem. Here's the setup for Vite, Create React App, or any other React SPA.

Project Structure

main.tsx
App.tsx
.env

Setup

Install Dependencies

pnpm add @thisbefine/analytics react-router-dom
npm install @thisbefine/analytics react-router-dom
yarn add @thisbefine/analytics react-router-dom

Set Up App Entry

src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { Analytics, BugReportFAB } from '@thisbefine/analytics/react';
import App from './App';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
      <Analytics
        apiKey={import.meta.env.VITE_THISBEFINE_API_KEY}
        debug={import.meta.env.DEV}
        config={{
          errors: {
            enabled: true,
            captureNetworkErrors: !import.meta.env.DEV,
          },
        }}
      />
      <BugReportFAB />
    </BrowserRouter>
  </React.StrictMode>
);

Using Create React App? Replace import.meta.env.VITE_THISBEFINE_API_KEY with process.env.REACT_APP_THISBEFINE_API_KEY.

Configure Router

src/App.tsx
import { Routes, Route } from 'react-router-dom';
import { Home } from './pages/Home';
import { Login } from './pages/Login';
import { Dashboard } from './pages/Dashboard';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/login" element={<Login />} />
      <Route path="/dashboard" element={<Dashboard />} />
    </Routes>
  );
}

export default App;

Done. Now let's add some actual tracking.

Authentication Example

Login Page

src/pages/Login.tsx
import { useIdentify, useTrack } from '@thisbefine/analytics/react';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

export const Login = () => {
  const identify = useIdentify();
  const trackLogin = useTrack('login_completed');
  const trackLoginFailed = useTrack('login_failed');
  const navigate = useNavigate();

  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password }),
      });

      if (!response.ok) {
        const data = await response.json();
        trackLoginFailed({ reason: data.error || 'invalid_credentials' });
        setError(data.message || 'Invalid credentials');
        return;
      }

      const { user, token } = await response.json();

      // Store token
      localStorage.setItem('token', token);

      // Identify the user
      identify(user.id, {
        email: user.email,
        name: user.name,
        plan: user.plan,
        createdAt: user.createdAt,
      });

      // Track the login
      trackLogin({ method: 'email' });

      // Off to the dashboard
      navigate('/dashboard');
    } catch (err) {
      trackLoginFailed({ reason: 'network_error' });
      setError('Network error. Please try again.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="login-page">
      <h1>Login</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="Email"
          required
        />
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          placeholder="Password"
          required
        />
        {error && <p className="error">{error}</p>}
        <button type="submit" disabled={loading}>
          {loading ? 'Logging in...' : 'Login'}
        </button>
      </form>
    </div>
  );
};

Logout Hook

src/hooks/useLogout.ts
import { useReset, useTrack } from '@thisbefine/analytics/react';
import { useNavigate } from 'react-router-dom';

export const useLogout = () => {
  const reset = useReset();
  const trackLogout = useTrack('logout');
  const navigate = useNavigate();

  const logout = async () => {
    // Track before reset
    trackLogout();

    // Clear local storage
    localStorage.removeItem('token');

    // Reset analytics
    reset();

    // Navigate to login
    navigate('/login');
  };

  return { logout };
};

Feature Tracking

Dashboard with Event Tracking

src/pages/Dashboard.tsx
import { useTrack, useLog } from '@thisbefine/analytics/react';
import { useState } from 'react';

export const Dashboard = () => {
  const trackExport = useTrack('data_exported');
  const trackFilter = useTrack('filter_applied');
  const log = useLog();

  const [filter, setFilter] = useState('all');
  const [exporting, setExporting] = useState(false);

  const handleFilterChange = (newFilter: string) => {
    setFilter(newFilter);
    trackFilter({ filter: newFilter, previousFilter: filter });
  };

  const handleExport = async (format: 'csv' | 'json' | 'pdf') => {
    setExporting(true);
    log('Export started', 'info', { format });

    try {
      const response = await fetch(`/api/export?format=${format}`);
      const blob = await response.blob();

      // Trigger download
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `data.${format}`;
      a.click();

      trackExport({
        format,
        filter,
        success: true,
      });

      log('Export completed', 'info', { format });
    } catch (error) {
      trackExport({
        format,
        filter,
        success: false,
        error: (error as Error).message,
      });

      log('Export failed', 'error', {
        format,
        error: (error as Error).message,
      });
    } finally {
      setExporting(false);
    }
  };

  return (
    <div className="dashboard">
      <h1>Dashboard</h1>

      <div className="filters">
        <select value={filter} onChange={(e) => handleFilterChange(e.target.value)}>
          <option value="all">All</option>
          <option value="active">Active</option>
          <option value="archived">Archived</option>
        </select>
      </div>

      <div className="export-buttons">
        <button onClick={() => handleExport('csv')} disabled={exporting}>
          Export CSV
        </button>
        <button onClick={() => handleExport('json')} disabled={exporting}>
          Export JSON
        </button>
        <button onClick={() => handleExport('pdf')} disabled={exporting}>
          Export PDF
        </button>
      </div>
    </div>
  );
};

Error Boundary

src/components/ErrorBoundary.tsx
import { Component, ReactNode } from 'react';
import { getAnalytics } from '@thisbefine/analytics';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error: Error | null;
}

export class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false, error: null };

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // Send to Thisbefine
    getAnalytics()?.captureException(error, {
      componentStack: errorInfo.componentStack,
      boundary: true,
    });
  }

  render() {
    if (this.state.hasError) {
      if (this.props.fallback) {
        return this.props.fallback;
      }

      return (
        <div style={{ padding: '2rem', textAlign: 'center' }}>
          <h2>Oops! Something went wrong.</h2>
          <p style={{ color: '#666' }}>{this.state.error?.message}</p>
          <button
            onClick={() => {
              this.setState({ hasError: false, error: null });
              window.location.reload();
            }}
          >
            Reload Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Wrap your app
// <ErrorBoundary>
//   <App />
// </ErrorBoundary>

Environment Variables

.env
VITE_THISBEFINE_API_KEY=tif_your_api_key

Access with import.meta.env.VITE_THISBEFINE_API_KEY

.env
REACT_APP_THISBEFINE_API_KEY=tif_your_api_key

Access with process.env.REACT_APP_THISBEFINE_API_KEY

Never commit your .env file to source control. Add it to .gitignore.

TypeScript Configuration

Using TypeScript with Vite? Add environment type declarations:

src/vite-env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_THISBEFINE_API_KEY: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

Now TypeScript knows about your environment variables. No more red squiggly lines.

On this page