React Grab Integration
Step 1: Detect Project Type
IMPORTANT: Before installing, detect the project type by checking these files:
| Check For | Project Type |
|---|---|
electron-vite.config.* |
electron-vite |
forge.config.* + vite.*.config.* |
Electron Forge + Vite |
forge.config.* + webpack.*.config.* |
Electron Forge + Webpack |
electron in package.json + webpack.config.* |
Electron + Webpack |
electron in package.json + vite.config.* |
Electron + Vite |
electron in package.json (no bundler) |
Plain Electron |
next.config.* |
Next.js |
vite.config.* (no electron) |
Vite |
webpack.config.* (no electron) |
Webpack |
Detection Commands:
# Check package.json for framework
cat package.json | grep -E "(electron|next|vite)"
# Check for config files
ls -la | grep -E "(electron-vite|forge|next|vite|webpack)\.config"
Step 2: Install
# Use the project's package manager (check lockfile)
pnpm add -D react-grab # if pnpm-lock.yaml exists
npm install -D react-grab # if package-lock.json exists
yarn add -D react-grab # if yarn.lock exists
Step 3: Configure Based on Project Type
A. Next.js
File to edit: app/layout.tsx (App Router) or pages/_app.tsx (Pages Router)
App Router - app/layout.tsx:
import Script from "next/script"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
{process.env.NODE_ENV === "development" && (
<Script src="//unpkg.com/react-grab/dist/index.global.js" strategy="beforeInteractive" />
)}
</head>
<body>{children}</body>
</html>
)
}
Pages Router - pages/_app.tsx:
import Script from "next/script"
import type { AppProps } from "next/app"
export default function App({ Component, pageProps }: AppProps) {
return (
<>
{process.env.NODE_ENV === "development" && (
<Script src="//unpkg.com/react-grab/dist/index.global.js" strategy="beforeInteractive" />
)}
<Component {...pageProps} />
</>
)
}
B. Vite (Web Only)
File to edit: src/main.tsx or src/index.tsx
import React from "react"
import ReactDOM from "react-dom/client"
import App from "./App"
// Add this block at the top, after imports
if (import.meta.env.DEV) {
import("react-grab").then(({ init }) => init())
}
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
C. electron-vite
Files to edit:
src/renderer/src/main.tsx- Initialize react-grabsrc/main/index.ts- Configure CSP
Step C1 - src/renderer/src/main.tsx:
// Add at the top, after imports
if (import.meta.env.DEV) {
import("react-grab").then(({ init }) => init())
}
Step C2 - src/main/index.ts: Find the BrowserWindow creation and add CSP:
import { app, BrowserWindow } from "electron"
import { join } from "path"
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: join(__dirname, "../preload/index.js"),
sandbox: false // Required for react-grab source mapping
}
})
// Add this block for development CSP
if (process.env.NODE_ENV === "development") {
mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
"Content-Security-Policy": [
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
]
}
})
})
}
// ... rest of window setup
}
D. Electron Forge + Vite
Files to edit:
src/renderer.tsorsrc/renderer/index.tsx- Initialize react-grabsrc/main.ts- Configure CSP
Step D1 - Renderer entry:
// Add at the top, after imports
if (import.meta.env.DEV) {
import("react-grab").then(({ init }) => init())
}
Step D2 - src/main.ts: Add CSP configuration (same as electron-vite Step C2)
E. Electron + Webpack
Files to edit:
src/renderer/index.tsx- Initialize react-grabsrc/main/main.ts- Configure CSP
Step E1 - src/renderer/index.tsx:
// Add at the top, after imports
if (process.env.NODE_ENV === "development") {
import("react-grab").then(({ init }) => init())
}
Step E2 - Main process: Add CSP configuration (same as electron-vite Step C2)
F. Plain Electron (No Bundler)
Files to edit:
index.html- Add script tagmain.js- Configure BrowserWindowrenderer.js- Initialize react-grab
Step F1 - index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline' unpkg.com; style-src 'self' 'unsafe-inline'" />
<script src="https://unpkg.com/react-grab/dist/index.global.js"></script>
</head>
<body>
<div id="root"></div>
<script src="./renderer.js"></script>
</body>
</html>
Step F2 - main.js:
const { app, BrowserWindow } = require("electron")
const path = require("path")
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
sandbox: false // Required for react-grab source mapping
}
})
mainWindow.loadFile("index.html")
if (process.env.NODE_ENV === "development") {
mainWindow.webContents.openDevTools()
}
}
app.whenReady().then(createWindow)
Step F3 - renderer.js:
// Initialize react-grab (loaded globally via script tag)
if (window.ReactGrab) {
window.ReactGrab.init()
}
Step 4: Verify Installation
Run the dev server and check:
- Open browser DevTools console
- Look for:
[react-grab] initializedmessage - Hover over any element and press Cmd+C (Mac) or Ctrl+C (Windows)
- Paste - you should see component info
Expected output:
<button class="btn">Submit</button>
in SubmitButton
components/button.tsx:42:19
Troubleshooting
All Projects
| Issue | Solution |
|---|---|
| "react-grab not defined" | Ensure script loads before React renders |
| Nothing copied on Cmd/Ctrl+C | Check browser console for errors |
| Wrong file paths | Enable source maps in bundler config |
Electron-Specific
| Issue | Solution |
|---|---|
| CSP blocking script | Add 'unsafe-inline' 'unsafe-eval' to CSP |
| Source maps not working | Set sandbox: false in webPreferences |
| Keyboard shortcut not working | Check for conflicting Menu accelerators |
| DevTools eval warning | Expected in dev - CSP needs 'unsafe-eval' |
CSP Reference for Electron
Via Session Headers (Recommended):
// In main process after creating BrowserWindow
if (process.env.NODE_ENV === "development") {
mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
"Content-Security-Policy": [
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
]
}
})
})
}
Via Meta Tag:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" />
Optional: Claude Code Integration
Add to package.json scripts:
| Project Type | Script |
|---|---|
| Next.js | "dev:grab": "npx @react-grab/claude-code@latest & next dev" |
| Vite | "dev:grab": "npx @react-grab/claude-code@latest & vite" |
| electron-vite | "dev:grab": "concurrently \"npx @react-grab/claude-code@latest\" \"electron-vite dev\"" |
| Electron Forge | "dev:grab": "concurrently \"npx @react-grab/claude-code@latest\" \"electron-forge start\"" |
Client-side (add after react-grab init):
if (import.meta.env.DEV) {
import("react-grab").then(() => {
const script = document.createElement("script")
script.src = "//unpkg.com/@react-grab/claude-code/dist/client.global.js"
document.head.appendChild(script)
})
}