Install guides / React Router and Remix

Bug reports from your React Router app

In React Router's framework mode, and in Remix, app/root.tsx renders the document for every route, so the widget goes there once. Use the tag for the quickest start, or the React components to say who is signed in.

The tag, in your root Layout

Put it after <Scripts />. It runs once from the server-rendered HTML; hydration leaves it and the widget's element alone.

app/root.tsx
import { Links, Meta, Scripts, ScrollRestoration } from "react-router"

export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
        <script
          src="https://app.brainbox.sh/widget.js"
          data-project="pk_your_project_key"
          data-endpoint="https://app.brainbox.sh/ingest"
        />
      </body>
    </html>
  )
}

On Remix, the imports come from @remix-run/react instead; the file is the same.

With the signed-in user

Load the user in the root loader and hand it to the React components. They start in an effect, so server rendering is unaffected.

npm install @ganeshdole/brainbox-widget @ganeshdole/brainbox-react @ganeshdole/brainbox-core
app/feedback.tsx
import type { ReactNode } from "react"
import { brainboxTransport } from "@ganeshdole/brainbox-core"
import { BrainboxProvider } from "@ganeshdole/brainbox-react"
import { BrainboxWidget } from "@ganeshdole/brainbox-widget"

const transport = brainboxTransport({
  endpoint: "https://app.brainbox.sh/ingest",
  projectKey: "pk_your_project_key",
})

type User = { id: string; email: string; name: string }

export function Feedback({ user, children }: { user: User | null; children: ReactNode }) {
  return (
    <BrainboxProvider transport={transport} identity={user ?? undefined}>
      {children}
      <BrainboxWidget />
    </BrainboxProvider>
  )
}
app/root.tsx
import { Outlet } from "react-router"
import type { Route } from "./+types/root"
import { Feedback } from "./feedback"
import { getUser } from "./auth.server" // yours

export async function loader({ request }: Route.LoaderArgs) {
  return { user: await getUser(request) }
}

export default function App({ loaderData }: Route.ComponentProps) {
  return (
    <Feedback user={loaderData.user}>
      <Outlet />
    </Feedback>
  )
}

On Remix, read the loader's data with useLoaderData() instead of the loaderData prop.

Your own button

Under <Feedback>, any route can open the widget with useBrainboxWidget():

import { useBrainboxWidget } from "@ganeshdole/brainbox-widget"

export function ReportProblemButton() {
  const { start } = useBrainboxWidget()
  return <button onClick={() => start("screenshot")}>Report a problem</button>
}

Passwords and personal data

Typed values are always masked in recordings, so passwords, emails and card numbers never leave the browser as plain text. Add the rr-block or rr-mask class to anything else you want kept out.

Try it before you install it

The button in the corner of this page is the real widget. Report something on this page and it lands in our inbox, the way your users’ reports will land in yours.

Get started