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:
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:
- Title - "Payment button doesn't work" (required)
- Description - The details (required)
- Severity - Low / Medium / High / Critical
- 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>Tracked Link
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.