From 3c7e54946aedc858e58a56b8bb8fb63123a778af Mon Sep 17 00:00:00 2001 From: Cody De Arkland Date: Mon, 22 Sep 2025 10:24:31 -0700 Subject: [PATCH 1/3] feat(react): add getting-started guide with StepConnector - Add comprehensive React getting-started guide with step-by-step flow - Fix StepConnector component quote style consistency (single -> double quotes) - Include error monitoring, logging, tracing, and replay configuration - Add React 19+ error handling with onCaughtError/onUncaughtError - Include optional customizations for replays and traces --- .../guides/react/getting-started.mdx | 336 ++++++++++++++++++ src/components/stepConnector/index.tsx | 44 +-- 2 files changed, 358 insertions(+), 22 deletions(-) create mode 100644 docs/platforms/javascript/guides/react/getting-started.mdx diff --git a/docs/platforms/javascript/guides/react/getting-started.mdx b/docs/platforms/javascript/guides/react/getting-started.mdx new file mode 100644 index 0000000000000..f56015755dd57 --- /dev/null +++ b/docs/platforms/javascript/guides/react/getting-started.mdx @@ -0,0 +1,336 @@ +--- +title: "Getting Started" +sidebar_order: 1 +description: "Learn how to get started with the extended functionality of the React SDK and Sentry." +--- + + + +This guide covers the full getting started pocess for the React SDK and assumes you want to utilize Error Monitoring, Logs, Tracing and Spans, and Replays. For the basic quickstart, the [React Quickstart](/platforms/javascript/guides/react/) guide is a better starting point. + + + + + +## Install + +Run the command for your preferred package manager to add the Sentry SDK to your application: + +```bash {tabTitle:npm} +npm install @sentry/react --save +``` + +### Add Readable Stack Traces With Source Maps (Optional) + + + +## Configure + +### Initialize the Sentry SDK + +To import and initialize Sentry, create a file in your project's root directory, for example, `instrument.js`, and add the following code: + +```javascript {filename:instrument.js} +import * as Sentry from "@sentry/react"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + // Adds request headers and IP for users, for more info visit: + // https://docs.sentry.io/platforms/javascript/guides/react/configuration/options/#sendDefaultPii + sendDefaultPii: true, + + integrations: [ + // ___PRODUCT_OPTION_START___ performance + Sentry.browserTracingIntegration(), + // ___PRODUCT_OPTION_END___ performance + // ___PRODUCT_OPTION_START___ session-replay + Sentry.replayIntegration(), + // ___PRODUCT_OPTION_END___ session-replay + // ___PRODUCT_OPTION_START___ user-feedback + Sentry.feedbackIntegration({ + // Additional SDK configuration goes in here, for example: + colorScheme: "system", + }), + // ___PRODUCT_OPTION_END___ user-feedback + // ___PRODUCT_OPTION_START___ logs + Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), + // ___PRODUCT_OPTION_END___ logs + ], + + // ___PRODUCT_OPTION_START___ logs + // For help logging, see: https://docs.sentry.io/platforms/javascript/guides/react/logging/ + enableLogs: true, + // ___PRODUCT_OPTION_END___ logs + + // ___PRODUCT_OPTION_START___ performance + // Set sample rate for performance monitoring + // We recommend adjusting this value in production + tracesSampleRate: 1.0, + // ___PRODUCT_OPTION_END___ performance + + // ___PRODUCT_OPTION_START___ session-replay + // For session replay + // See: https://docs.sentry.io/platforms/javascript/session-replay/ + replaysSessionSampleRate: 0.1, // Sample 10% of sessions + replaysOnErrorSampleRate: 1.0, // Sample 100% of sessions with an error + // ___PRODUCT_OPTION_END___ session-replay + + // ___PRODUCT_OPTION_START___ logs + // For help logging, see: https://docs.sentry.io/platforms/javascript/guides/react/logging/ + extraErrorDataIntegration: false, + // ___PRODUCT_OPTION_END___ logs +}); +``` + +### Import & Use the Instrument file + +Import the `instrument.js` file in your application's entry point _before all other imports_ to initialize the SDK: + +```javascript {filename:index.jsx} {1} +import "./instrument.js"; +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; + +const container = document.getElementById("root"); +const root = createRoot(container); +root.render(); +``` + +## Capture React Errors + +To make sure Sentry captures all your app's errors, configure error handling based on your React version. + +### React 19+ + +Starting with React 19, use the `onCaughtError` and `onUncaughtError` root options to capture React errors: + +```javascript {9-15} {filename:index.jsx} +import "./instrument.js"; +import * as Sentry from "@sentry/react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; + +const container = document.getElementById("root"); +const root = createRoot(container, { + // Callback for errors caught by React error boundaries + onCaughtError: Sentry.reactErrorHandler((error, errorInfo) => { + console.error("Caught error:", error, errorInfo.componentStack); + }), + // Callback for errors not caught by React error boundaries + onUncaughtError: Sentry.reactErrorHandler(), +}); +root.render(); +``` + + + +### React 16 - 18 + +Use the Sentry Error Boundary to wrap your application: + +```javascript {filename:index.jsx} +import React from "react"; +import * as Sentry from "@sentry/react"; + +An error has occurred

} showDialog> + +
; +``` + + + Alternatively, if you're using a class component, you can wrap your application + with `Sentry.withErrorBoundary`. Find out more + [here](features/error-boundary/#manually-capturing-errors). + + +
+ +## Sending Logs + +[Structured logging](/platforms/javascript/guides/react/logs/) lets users send text-based log information from their applications to Sentry. Once in Sentry, these logs can be viewed alongside relevant errors, searched by text-string, or searched using their individual attributes. + +Use Sentry's logger to capture structured logs with meaningful attributes that help you debug issues and understand user behavior. + +```javascript +import * as Sentry from "@sentry/react"; + +const { logger } = Sentry; + +// Send structured logs with attributes +logger.info("User completed checkout", { + userId: 123, + orderId: "order_456", + amount: 99.99 +}); + +logger.error("Payment processing failed", { + errorCode: "CARD_DECLINED", + userId: 123, + attemptCount: 3 +}); + +// Using template literals for dynamic data +logger.warn(logger.fmt`Rate limit exceeded for user: ${userId}`); +``` + +## Customizing Replays (Optional) + +[Replays](/product/explore/session-replay/web/getting-started/) allow you to see video-like reproductions of user sessions. + +By default, Session Replay masks sensitive data for privacy and to protect PII data. You can modify the replay configurations in your client-side Sentry initialization to show (unmask) specific content that's safe to display. + +```javascript {filename:instrument.js} +import * as Sentry from "@sentry/react"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [ + Sentry.replayIntegration({ + // This will show the content of the div with the class "reveal-content" and the span with the data-safe-to-show attribute + unmask: [".reveal-content", "[data-safe-to-show]"], + // This will show all text content in replays. Use with caution. + maskAllText: false, + // This will show all media content in replays. Use with caution. + blockAllMedia: false, + }), + ], + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + // ... your existing config +}); +``` + +```jsx +
This content will be visible in replays
+Safe user data: {username} +``` + +## Custom Traces with Attributes (Optional) + +[Tracing](/platforms/javascript/guides/react/tracing/) allows you to monitor interactions between multiple services or applications. Create custom spans to measure specific operations and add meaningful attributes. This helps you understand performance bottlenecks and debug issues with detailed context. + +```javascript +import * as Sentry from "@sentry/react"; + +// Create custom spans to measure specific operations +async function processUserData(userId) { + return await Sentry.startSpan( + { + name: "Process User Data", + op: "function", + attributes: { + userId: userId, + operation: "data_processing", + version: "2.1" + } + }, + async () => { + // Your business logic here + const userData = await fetchUserData(userId); + + // Nested span for specific operations + return await Sentry.startSpan( + { + name: "Transform Data", + op: "transform", + attributes: { + recordCount: userData.length, + transformType: "normalize" + } + }, + () => { + return transformUserData(userData); + } + ); + } + ); +} + +// Add attributes to existing spans +const span = Sentry.getActiveSpan(); +if (span) { + span.setAttributes({ + cacheHit: true, + region: "us-west-2", + performanceScore: 0.95 + }); +} +``` + +## Avoid Ad Blockers With Tunneling (Optional) + + + +## Verify Your Setup + +Let's test your setup and confirm that Sentry is working correctly and sending data to your Sentry project. + +### Test Error Capturing + +Add the following test button to one of your pages, which will trigger an error that Sentry will capture when you click it: + +```javascript + +``` + +Open the page in a browser (for most React applications, this will be at localhost) and click the button to trigger a frontend error. + + + +### View Captured Data in Sentry + +Now, head over to your project on [Sentry.io](https://sentry.io/) to view the collected data (it takes a couple of moments for the data to appear). + + + +
+ +## Next Steps + +At this point, you should have integrated Sentry into your React application and should already be sending error and performance data to your Sentry project. + +Now's a good time to customize your setup and look into more advanced topics: + +- Extend Sentry to your backend using one of our [SDKs](/) +- Continue to customize your configuration +- Learn more about the React Error Boundary +- Learn how to manually capture errors +- Avoid ad-blockers with tunneling + +## Additional Resources + + + +If you're using `react-router` in your application, you need to set up the Sentry integration for your specific React Router version to trace `navigation` events. + +Select your React Router version to start instrumenting your routing: + +- [React Router v7 (library mode)](features/react-router/v7) +- [React Router v6](features/react-router/v6) +- [Older React Router versions](features/react-router) +- [TanStack Router](features/tanstack-router) + + + + + +To capture Redux state data, use `Sentry.createReduxEnhancer` when initializing your Redux store. + + + + + + + +- [Get support](https://sentry.zendesk.com/hc/en-us/) + + diff --git a/src/components/stepConnector/index.tsx b/src/components/stepConnector/index.tsx index 795b4430ed45f..003249f7c1bf1 100644 --- a/src/components/stepConnector/index.tsx +++ b/src/components/stepConnector/index.tsx @@ -1,4 +1,4 @@ -'use client'; +"use client"; /** * Component: StepConnector / StepComponent @@ -27,11 +27,11 @@ * - --rail-x, --circle, --gap control rail position and circle size/spacing. */ -import {useEffect, useMemo, useRef, useState} from 'react'; +import {useEffect, useMemo, useRef, useState} from "react"; -import styles from './style.module.scss'; +import styles from "./style.module.scss"; -type Persistence = 'session' | 'none'; +type Persistence = "session" | "none"; type Props = { children: React.ReactNode; @@ -52,19 +52,19 @@ type Props = { export function StepComponent({ children, startAt = 1, - selector = 'h2', + selector = "h2", showNumbers = true, checkable = false, - persistence = 'session', + persistence = "session", showReset = true, }: Props) { const containerRef = useRef(null); const [completed, setCompleted] = useState>(new Set()); const storageKey = useMemo(() => { - if (typeof window === 'undefined' || persistence !== 'session') return null; + if (typeof window === "undefined" || persistence !== "session") return null; try { - const path = window.location?.pathname ?? ''; + const path = window.location?.pathname ?? ""; return `stepConnector:${path}:${selector}:${startAt}`; } catch { return null; @@ -84,24 +84,24 @@ export function StepComponent({ headings.forEach(h => { h.classList.remove(styles.stepHeading); - h.removeAttribute('data-step'); - h.removeAttribute('data-completed'); + h.removeAttribute("data-step"); + h.removeAttribute("data-completed"); const existingToggle = h.querySelector(`.${styles.stepToggle}`); if (existingToggle) existingToggle.remove(); }); headings.forEach((h, idx) => { const stepNumber = startAt + idx; - h.setAttribute('data-step', String(stepNumber)); + h.setAttribute("data-step", String(stepNumber)); h.classList.add(styles.stepHeading); if (checkable) { - const btn = document.createElement('button'); - btn.type = 'button'; + const btn = document.createElement("button"); + btn.type = "button"; btn.className = styles.stepToggle; - btn.setAttribute('aria-label', `Toggle completion for step ${stepNumber}`); - btn.setAttribute('aria-pressed', completed.has(h.id) ? 'true' : 'false'); - btn.addEventListener('click', () => { + btn.setAttribute("aria-label", `Toggle completion for step ${stepNumber}`); + btn.setAttribute("aria-pressed", completed.has(h.id) ? "true" : "false"); + btn.addEventListener("click", () => { setCompleted(prev => { const next = new Set(prev); if (next.has(h.id)) next.delete(h.id); @@ -117,8 +117,8 @@ export function StepComponent({ return () => { headings.forEach(h => { h.classList.remove(styles.stepHeading); - h.removeAttribute('data-step'); - h.removeAttribute('data-completed'); + h.removeAttribute("data-step"); + h.removeAttribute("data-completed"); const existingToggle = h.querySelector(`.${styles.stepToggle}`); if (existingToggle) existingToggle.remove(); }); @@ -145,10 +145,10 @@ export function StepComponent({ ); headings.forEach(h => { const isDone = completed.has(h.id); - if (isDone) h.setAttribute('data-completed', 'true'); - else h.removeAttribute('data-completed'); + if (isDone) h.setAttribute("data-completed", "true"); + else h.removeAttribute("data-completed"); const btn = h.querySelector(`.${styles.stepToggle}`) as HTMLButtonElement | null; - if (btn) btn.setAttribute('aria-pressed', isDone ? 'true' : 'false'); + if (btn) btn.setAttribute("aria-pressed", isDone ? "true" : "false"); }); if (storageKey && checkable) { @@ -175,7 +175,7 @@ export function StepComponent({
{checkable && showReset && (
From 582fb6eda14fcab0d822ec6facd7c8e51c4ad1c3 Mon Sep 17 00:00:00 2001 From: Cody De Arkland Date: Mon, 22 Sep 2025 10:35:50 -0700 Subject: [PATCH 2/3] fix(stepConnector): align quote style with project Prettier config - Revert to single quotes to match project's singleQuote: true setting - Ensures consistency with automated formatting --- src/components/stepConnector/index.tsx | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/stepConnector/index.tsx b/src/components/stepConnector/index.tsx index 003249f7c1bf1..b791f54a8c0bd 100644 --- a/src/components/stepConnector/index.tsx +++ b/src/components/stepConnector/index.tsx @@ -1,4 +1,4 @@ -"use client"; +'use client'; /** * Component: StepConnector / StepComponent @@ -27,11 +27,11 @@ * - --rail-x, --circle, --gap control rail position and circle size/spacing. */ -import {useEffect, useMemo, useRef, useState} from "react"; +import {useEffect, useMemo, useRef, useState} from 'react'; -import styles from "./style.module.scss"; +import styles from './style.module.scss'; -type Persistence = "session" | "none"; +type Persistence = 'session' | 'none'; type Props = { children: React.ReactNode; @@ -47,24 +47,24 @@ type Props = { showReset?: boolean; /** Start numbering from this value. @defaultValue 1 */ startAt?: number; -}; +} export function StepComponent({ children, startAt = 1, - selector = "h2", + selector = 'h2', showNumbers = true, checkable = false, - persistence = "session", + persistence = 'session', showReset = true, }: Props) { const containerRef = useRef(null); const [completed, setCompleted] = useState>(new Set()); const storageKey = useMemo(() => { - if (typeof window === "undefined" || persistence !== "session") return null; + if (typeof window === 'undefined' || persistence !== 'session') return null; try { - const path = window.location?.pathname ?? ""; + const path = window.location?.pathname ?? ''; return `stepConnector:${path}:${selector}:${startAt}`; } catch { return null; @@ -84,24 +84,24 @@ export function StepComponent({ headings.forEach(h => { h.classList.remove(styles.stepHeading); - h.removeAttribute("data-step"); - h.removeAttribute("data-completed"); + h.removeAttribute('data-step'); + h.removeAttribute('data-completed'); const existingToggle = h.querySelector(`.${styles.stepToggle}`); if (existingToggle) existingToggle.remove(); }); headings.forEach((h, idx) => { const stepNumber = startAt + idx; - h.setAttribute("data-step", String(stepNumber)); + h.setAttribute('data-step', String(stepNumber)); h.classList.add(styles.stepHeading); if (checkable) { - const btn = document.createElement("button"); - btn.type = "button"; + const btn = document.createElement('button'); + btn.type = 'button'; btn.className = styles.stepToggle; - btn.setAttribute("aria-label", `Toggle completion for step ${stepNumber}`); - btn.setAttribute("aria-pressed", completed.has(h.id) ? "true" : "false"); - btn.addEventListener("click", () => { + btn.setAttribute('aria-label', `Toggle completion for step ${stepNumber}`); + btn.setAttribute('aria-pressed', completed.has(h.id) ? 'true' : 'false'); + btn.addEventListener('click', () => { setCompleted(prev => { const next = new Set(prev); if (next.has(h.id)) next.delete(h.id); @@ -117,8 +117,8 @@ export function StepComponent({ return () => { headings.forEach(h => { h.classList.remove(styles.stepHeading); - h.removeAttribute("data-step"); - h.removeAttribute("data-completed"); + h.removeAttribute('data-step'); + h.removeAttribute('data-completed'); const existingToggle = h.querySelector(`.${styles.stepToggle}`); if (existingToggle) existingToggle.remove(); }); @@ -145,10 +145,10 @@ export function StepComponent({ ); headings.forEach(h => { const isDone = completed.has(h.id); - if (isDone) h.setAttribute("data-completed", "true"); - else h.removeAttribute("data-completed"); + if (isDone) h.setAttribute('data-completed', 'true'); + else h.removeAttribute('data-completed'); const btn = h.querySelector(`.${styles.stepToggle}`) as HTMLButtonElement | null; - if (btn) btn.setAttribute("aria-pressed", isDone ? "true" : "false"); + if (btn) btn.setAttribute('aria-pressed', isDone ? 'true' : 'false'); }); if (storageKey && checkable) { @@ -175,7 +175,7 @@ export function StepComponent({
{checkable && showReset && (
From 741a29d697a4775d2dec6f69a82decb240423c42 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:36:38 +0000 Subject: [PATCH 3/3] [getsentry/action-github-commit] Auto commit --- src/components/stepConnector/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/stepConnector/index.tsx b/src/components/stepConnector/index.tsx index b791f54a8c0bd..795b4430ed45f 100644 --- a/src/components/stepConnector/index.tsx +++ b/src/components/stepConnector/index.tsx @@ -47,7 +47,7 @@ type Props = { showReset?: boolean; /** Start numbering from this value. @defaultValue 1 */ startAt?: number; -} +}; export function StepComponent({ children,