React Integration
Best practices for integrating Apperio with React applications.
Setup
Install the SDK and initialize it at the root of your React application, before any components render:
Bash
npm install apperiosrc/index.tsx
import React from class="syntax-string">"react";
import ReactDOM from class="syntax-string">"react-dom/client";
import Apperio from class="syntax-string">"apperio";
import App from class="syntax-string">"./App";
class="syntax-comment">// Initialize BEFORE rendering
Apperio.init({
projectId: process.env.REACT_APP_APPERIO_PROJECT_ID!,
apiKey: process.env.REACT_APP_APPERIO_API_KEY!,
environment: process.env.NODE_ENV,
autoCapture: {
errors: true,
performance: true,
network: true,
console: false,
pageviews: true,
},
});
const root = ReactDOM.createRoot(document.getElementById(class="syntax-string">"root")!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);ApperioProvider Pattern
Create a context provider to make Apperio accessible throughout your component tree and to handle initialization lifecycle:
src/providers/ApperioProvider.tsx
import { createContext, useContext, useEffect, useRef } from class="syntax-string">"react";
import Apperio from class="syntax-string">"apperio";
interface ApperioContextValue {
log: typeof Apperio;
}
const ApperioContext = createContext<ApperioContextValue | null>(null);
export function ApperioProvider({
children,
projectId,
apiKey,
}: {
children: React.ReactNode;
projectId: string;
apiKey: string;
}) {
const initialized = useRef(false);
useEffect(() => {
if (!initialized.current) {
Apperio.init({
projectId,
apiKey,
environment: process.env.NODE_ENV,
autoCapture: {
errors: true,
performance: true,
network: true,
},
});
initialized.current = true;
}
class="syntax-comment">// Flush on unmount
return () => {
Apperio.flush();
};
}, [projectId, apiKey]);
return (
<ApperioContext.Provider value={{ log: Apperio }}>
{children}
</ApperioContext.Provider>
);
}
export function useApperio() {
const context = useContext(ApperioContext);
if (!context) {
throw new Error(class="syntax-string">"useApperio must be used within a ApperioProvider");
}
return context.log;
}Use the provider in your app root:
src/App.tsx
import { ApperioProvider } from class="syntax-string">"./providers/ApperioProvider";
import { Dashboard } from class="syntax-string">"./pages/Dashboard";
function App() {
return (
<ApperioProvider
projectId={process.env.REACT_APP_APPERIO_PROJECT_ID!}
apiKey={process.env.REACT_APP_APPERIO_API_KEY!}
>
<Dashboard />
</ApperioProvider>
);
}Error Boundaries
Create an error boundary that automatically reports rendering errors to Apperio:
src/components/ApperioErrorBoundary.tsx
import React from class="syntax-string">"react";
import Apperio from class="syntax-string">"apperio";
interface Props {
children: React.ReactNode;
fallback?: React.ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ApperioErrorBoundary extends React.Component<Props, State> {
state: State = { hasError: false, error: null };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
Apperio.error(class="syntax-string">"React component error", {
error: {
name: error.name,
message: error.message,
stack: error.stack,
},
componentStack: info.componentStack,
});
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div style={{ padding: class="syntax-string">"20px", textAlign: class="syntax-string">"center" }}>
<h2>Something went wrong</h2>
<p>{this.state.error?.message}</p>
</div>
);
}
return this.props.children;
}
}
class="syntax-comment">// Usage:
class="syntax-comment">// <ApperioErrorBoundary fallback={<ErrorFallback />}>
class="syntax-comment">// <MyComponent />
class="syntax-comment">// </ApperioErrorBoundary>Tip
Wrap individual feature sections with separate error boundaries. This way, a crash in one section does not take down the entire application, and each crash is reported with the correct component context.
Custom Hook Usage
Create custom hooks for common logging patterns:
src/hooks/useApperioAction.ts
import { useCallback } from class="syntax-string">"react";
import Apperio from class="syntax-string">"apperio";
class="syntax-comment">/**
* Hook for logging user actions with consistent formatting
*/
export function useApperioAction(component: string) {
const logAction = useCallback(
(action: string, data?: Record<string, any>) => {
Apperio.info(component + class="syntax-string">": " + action, {
component,
action,
...data,
});
},
[component]
);
const logError = useCallback(
(action: string, error: unknown, data?: Record<string, any>) => {
Apperio.error(component + class="syntax-string">": " + action + class="syntax-string">" failed", {
component,
action,
error,
...data,
});
},
[component]
);
return { logAction, logError };
}
class="syntax-comment">// Usage in a component:
function CheckoutPage() {
const { logAction, logError } = useApperioAction(class="syntax-string">"CheckoutPage");
const handleSubmit = async (order: Order) => {
logAction(class="syntax-string">"submit_order", { orderId: order.id });
try {
await submitOrder(order);
logAction(class="syntax-string">"order_success", { orderId: order.id });
} catch (err) {
logError(class="syntax-string">"submit_order", err, { orderId: order.id });
}
};
return <form onSubmit={handleSubmit}>...</form>;
}Component-Level Logging
Log component lifecycle events for debugging render issues:
TypeScript
import { useEffect } from class="syntax-string">"react";
import Apperio from class="syntax-string">"apperio";
function UserProfile({ userId }: { userId: string }) {
class="syntax-comment">// Log component mount/unmount
useEffect(() => {
Apperio.debug(class="syntax-string">"UserProfile mounted", { userId });
return () => {
Apperio.debug(class="syntax-string">"UserProfile unmounted", { userId });
};
}, [userId]);
class="syntax-comment">// Log data fetching
useEffect(() => {
Apperio.debug(class="syntax-string">"Fetching user data", { userId });
fetchUser(userId)
.then((user) => {
Apperio.info(class="syntax-string">"User data loaded", {
userId,
loadTime: performance.now(),
});
})
.catch((err) => {
Apperio.error(class="syntax-string">"Failed to load user data", {
userId,
error: err,
});
});
}, [userId]);
return <div>...</div>;
}Performance Tracking
Track component render performance with the Performance API:
TypeScript
import { useEffect, useRef } from class="syntax-string">"react";
import Apperio from class="syntax-string">"apperio";
function useRenderPerformance(componentName: string) {
const renderCount = useRef(class="syntax-number">0);
const startTime = useRef(performance.now());
useEffect(() => {
renderCount.current += class="syntax-number">1;
const renderTime = performance.now() - startTime.current;
if (renderTime > class="syntax-number">16) {
class="syntax-comment">// Longer than one frame(60fps)
Apperio.warn(class="syntax-string">"Slow render detected", {
component: componentName,
renderTime: Math.round(renderTime),
renderCount: renderCount.current,
});
}
startTime.current = performance.now();
});
}
class="syntax-comment">// Usage:
function HeavyComponent() {
useRenderPerformance(class="syntax-string">"HeavyComponent");
return <div>...</div>;
}Best Practices
- Initialize once -- Call
Apperio.init()in your entry file, not inside components. Use a ref guard to prevent double-init in StrictMode. - Use error boundaries -- Wrap feature sections with
ApperioErrorBoundaryfor automatic React error reporting. - Log meaningful context -- Include component names, user IDs, and relevant state in log data.
- Avoid over-logging -- Use
debuglevel for development details and setlogLevel: "info"in production. - Flush on navigation -- Call
Apperio.flush()before page unload or significant navigation events.