Thisbefine
React

React Components

Drop-in components for the lazy (in a good way)

React Components

Pre-built components you can drop into your app. Because sometimes you don't want to build everything from scratch. We get it.

BugReportFAB

A floating action button that lets users report bugs without you having to build a bug report form. It's like a "Report Bug" button that actually captures useful context.

Import

// For Next.js
import { BugReportFAB } from '@thisbefine/analytics/next';

// For React (Vite, CRA, etc.)
import { BugReportFAB } from '@thisbefine/analytics/react';

Usage

Put it in your layout. Watch bug reports roll in with actual context:

app/layout.tsx
import { Analytics, BugReportFAB } from '@thisbefine/analytics/next';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics apiKey="tif_xxx" />
        <BugReportFAB />
      </body>
    </html>
  );
}

Props

Prop

Type

Examples

Move it to the other corner:

<BugReportFAB position="bottom-left" />

Make it your brand color:

// Hex
<BugReportFAB buttonColor="#3b82f6" />

// CSS variable (chef's kiss)
<BugReportFAB buttonColor="var(--primary)" />

// Named color (we don't judge)
<BugReportFAB buttonColor="blue" />

Custom text:

<BugReportFAB buttonText="Report Issue" />
<BugReportFAB buttonText="Found a Bug?" />
<BugReportFAB buttonText="Feedback" />
<BugReportFAB buttonText="Help!" />

Full customization:

<BugReportFAB
  position="bottom-left"
  buttonColor="#10b981"
  buttonText="Something's Wrong"
/>

What Users Get

When they click the button, a modal opens with:

  1. Title - "Payment button doesn't work" (required)
  2. Description - The details (required)
  3. Severity - Low / Medium / High / Critical
  4. Screenshot - Upload or Ctrl+V to paste

What you automatically get with each report:

  • Current URL
  • Browser & user agent
  • Operating system
  • User ID (if they're logged in)
  • Anonymous ID (if they're not)
  • Timestamp

No more "it doesn't work" with zero context. You'll know exactly what doesn't work, where, and for whom.

Conditional Rendering

Hide it where it doesn't make sense:

'use client';

import { usePathname } from 'next/navigation';
import { BugReportFAB } from '@thisbefine/analytics/next';

const ConditionalBugReport = () => {
  const pathname = usePathname();

  // No bug reports on auth pages (users aren't even in yet)
  if (pathname.startsWith('/login') || pathname.startsWith('/signup')) {
    return null;
  }

  return <BugReportFAB />;
};

Or show it only for certain users:

const BugReportForPremium = () => {
  const { user } = useUser();

  // Free tier users get support articles
  // Premium users get direct bug reporting
  if (user?.plan !== 'premium') {
    return null;
  }

  return <BugReportFAB />;
};

Dealing with Overlap

The FAB uses position: fixed with a high z-index. If it's stepping on your other UI:

Option 1: Add padding to your content

main {
  padding-bottom: 5rem; /* Give the FAB room */
}

Option 2: Move it

<BugReportFAB position="bottom-left" />

Option 3: Hide it on certain pages (see conditional rendering above)


Programmatic Widget

For when you want to control the widget yourself instead of using the React component.

Import

import { createBugReportWidget } from '@thisbefine/analytics';

Usage

import { createBugReportWidget, getAnalytics } from '@thisbefine/analytics';

const analytics = getAnalytics();

if (analytics) {
  const widget = createBugReportWidget(analytics, {
    position: 'bottom-right',
    buttonColor: '#ef4444',
    buttonText: 'Report Bug',
  });

  // Later, when you're done:
  widget.destroy();
}

Options

interface BugReportWidgetOptions {
  position?: 'bottom-right' | 'bottom-left';
  buttonColor?: string;
  buttonText?: string;
}

Why Use This?

Sometimes the React component lifecycle isn't what you want:

Show/hide based on external state:

'use client';

import { useEffect } from 'react';
import { createBugReportWidget, getAnalytics } from '@thisbefine/analytics';

const DynamicBugReport = ({ enabled }: { enabled: boolean }) => {
  useEffect(() => {
    if (!enabled) return;

    const analytics = getAnalytics();
    if (!analytics) return;

    const widget = createBugReportWidget(analytics);

    // Clean up when disabled or unmounted
    return () => {
      widget.destroy();
    };
  }, [enabled]);

  return null;
};

Show after a delay:

useEffect(() => {
  // Let users explore for 30 seconds before showing
  const timer = setTimeout(() => {
    const analytics = getAnalytics();
    if (analytics) {
      createBugReportWidget(analytics);
    }
  }, 30000);

  return () => clearTimeout(timer);
}, []);

Building Your Own Components

Don't like our components? Build your own with the hooks. No hard feelings.

Track Button

A button that tracks clicks automatically:

import { useTrack } from '@thisbefine/analytics/next';  // or /react for non-Next.js

interface TrackButtonProps {
  event: string;
  properties?: Record<string, unknown>;
  children: React.ReactNode;
}

export const TrackButton = ({
  event,
  properties,
  children,
  ...props
}: TrackButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
  const track = useTrack(event);

  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    track(properties);
    props.onClick?.(e);
  };

  return (
    <button {...props} onClick={handleClick}>
      {children}
    </button>
  );
};

// Usage
<TrackButton event="cta_clicked" properties={{ location: 'hero' }}>
  Get Started
</TrackButton>

Auto-track link clicks:

import { useTrack } from '@thisbefine/analytics/next';
import Link from 'next/link';

interface TrackedLinkProps {
  href: string;
  event?: string;
  children: React.ReactNode;
}

export const TrackedLink = ({
  href,
  event = 'link_clicked',
  children,
  ...props
}: TrackedLinkProps & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>) => {
  const track = useTrack(event);

  const handleClick = () => {
    track({
      href,
      text: typeof children === 'string' ? children : undefined,
    });
  };

  return (
    <Link href={href} onClick={handleClick} {...props}>
      {children}
    </Link>
  );
};

Error Boundary with Tracking

Catch React errors and send them to Thisbefine:

'use client';

import { Component, ReactNode } from 'react';
import { getAnalytics } from '@thisbefine/analytics';

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

interface State {
  hasError: boolean;
}

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

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // Send to Thisbefine before showing fallback
    getAnalytics()?.captureException(error, {
      componentStack: errorInfo.componentStack,
      boundary: 'TrackedErrorBoundary',
    });
  }

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

// Usage
<TrackedErrorBoundary fallback={<ErrorPage />}>
  <YourApp />
</TrackedErrorBoundary>

When building custom components, use the hooks from @thisbefine/analytics/next (for Next.js) or @thisbefine/analytics/react (for other frameworks). They handle memoization properly so you don't have to think about it.

On this page