Skip to content

React Integration

Create a shared client instance and use it across your app.

// src/gunsole.ts — singleton client
import { createGunsoleClient } from "@gunsole/web";
export const gunsole = createGunsoleClient({
projectId: "my-react-app",
mode: "local",
env: "development",
appName: "React Vite App",
appVersion: "1.0.0",
defaultTags: { framework: "react", bundler: "vite" },
});

Import gunsole wherever you need it:

src/components/UserList.tsx
import { gunsole } from "../gunsole";
import { useEffect, useState } from "react";
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("/api/users")
.then((res) => res.json())
.then((data) => {
setUsers(data);
gunsole.info({
bucket: "ui",
message: `Loaded ${data.length} users`,
tags: { component: "UserList", action: "fetch" },
});
})
.catch((err) => {
gunsole.error({
bucket: "ui",
message: "Failed to load users",
context: { error: err.message },
tags: { component: "UserList", action: "fetch" },
});
});
}, []);
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}

Log fatal errors caught by React error boundaries:

import { gunsole } from "./gunsole";
class GunsoleErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
gunsole.fatal({
bucket: "fatal",
message: error.message,
context: {
name: error.name,
stack: error.stack,
componentStack: info.componentStack,
},
});
}
render() {
if (this.state.hasError) return <div>Something went wrong.</div>;
return this.props.children;
}
}
createRoot(document.getElementById("root")!).render(
<GunsoleErrorBoundary>
<App />
</GunsoleErrorBoundary>
);
useEffect(() => {
gunsole.info({
bucket: "app_lifecycle",
message: "App mounted",
context: { framework: "react", router: "tanstack" },
});
}, []);
gunsole.setUser({ id: userId, email: "[email protected]" });
const fetchPokemon = async () => {
const traceId = `trace-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
const startTime = performance.now();
gunsole.info({
bucket: "api_request",
message: `Fetching Pokemon: ${pokemonName}`,
context: { pokemon: pokemonName },
tags: { api: "pokeapi", action: "fetch_start" },
traceId,
});
try {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName}`);
const fetchTime = performance.now() - startTime;
if (!response.ok) throw new Error(`Pokemon not found: ${pokemonName}`);
const data = await response.json();
const totalTime = performance.now() - startTime;
gunsole.info({
bucket: "api_request",
message: `Pokemon fetched successfully: ${data.name}`,
context: {
pokemon: data.name,
pokemonId: data.id,
fetchTimeMs: Math.round(fetchTime),
totalTimeMs: Math.round(totalTime),
},
tags: { api: "pokeapi", action: "fetch_success", status: "200" },
traceId,
});
} catch (err) {
const totalTime = performance.now() - startTime;
gunsole.error({
bucket: "api_request",
message: `Failed to fetch Pokemon: ${pokemonName}`,
context: {
pokemon: pokemonName,
error: err instanceof Error ? err.message : "Unknown error",
totalTimeMs: Math.round(totalTime),
},
tags: { api: "pokeapi", action: "fetch_error" },
traceId,
});
}
};

Track Core Web Vitals with gunsole:

import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals";
import { gunsole } from "./gunsole";
useEffect(() => {
const report = (metric: { name: string; value: number; rating: string }) => {
gunsole.info({
bucket: "web_vitals",
message: `${metric.name}: ${metric.value.toFixed(2)}`,
context: { value: metric.value, rating: metric.rating },
tags: { metric: metric.name, rating: metric.rating },
});
};
onCLS(report);
onFCP(report);
onINP(report);
onLCP(report);
onTTFB(report);
}, []);
await gunsole.flush();