Skip to content

Conversation

@ian
Copy link

@ian ian commented Nov 2, 2025

OpenPanel React Provider

A React context provider for OpenPanel analytics built on @openpanel/sdk.

This follows the same architecture as the official React Native SDK and is designed to be contributed back to the OpenPanel project.

Inspiration

We needed this component for https://startupkit.com, as we're bundling both Posthog and OpenPanel by default with new client installations.

Installation

npm install @openpanel/react
# or
pnpm add @openpanel/react
# or
yarn add @openpanel/react

Peer Dependencies:

This package requires React 18+ or React 19+:

{
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0",
    "react-dom": "^18.0.0 || ^19.0.0"
  }
}

Usage

Basic Setup (Next.js App Router)

Wrap your app with the OpenPanelProvider in your root layout:

// app/layout.tsx
import { OpenPanelProvider } from '@openpanel/react';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <OpenPanelProvider clientId="your-client-id">
          {children}
        </OpenPanelProvider>
      </body>
    </html>
  );
}

Basic Setup (Next.js Pages Router)

// pages/_app.tsx
import { OpenPanelProvider } from '@openpanel/react';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <OpenPanelProvider clientId="your-client-id">
      <Component {...pageProps} />
    </OpenPanelProvider>
  );
}

Using Environment Variables

The provider automatically reads from environment variables if no clientId prop is provided:

<OpenPanelProvider>
  {children}
</OpenPanelProvider>

It will check for (in order):

  1. NEXT_PUBLIC_OPENPANEL_CLIENT_ID (Next.js client-side)
  2. OPENPANEL_CLIENT_ID (server-side or other environments)

Example .env.local:

NEXT_PUBLIC_OPENPANEL_CLIENT_ID=your_client_id_here

Configuration Options

The provider accepts all options from OpenPanelOptions:

<OpenPanelProvider
  clientId="your-client-id"
  trackScreenViews={false}
  trackOutgoingLinks={true}
  trackAttributes={true}
  apiUrl="https://api.openpanel.dev"
  // ... any other OpenPanelOptions
>
  {children}
</OpenPanelProvider>

Using the Hook

Access the OpenPanel client in any component using the useOpenPanel() hook:

'use client';

import { useOpenPanel } from '@openpanel/react';

export function SubscribeButton() {
  const openpanel = useOpenPanel();

  const handleClick = () => {
    openpanel?.track('button_clicked', {
      button_name: 'subscribe',
      page: 'homepage'
    });
  };

  return <button onClick={handleClick}>Subscribe</button>;
}

API Methods

The hook returns the OpenPanel client instance with all SDK methods:

Track Events

const openpanel = useOpenPanel();

// Track a simple event
openpanel?.track('button_clicked');

// Track with properties
openpanel?.track('purchase', {
  product_id: 'prod-123',
  amount: 99.99,
  currency: 'USD'
});

Identify Users

// Identify a user
openpanel?.identify({
  profileId: 'user-123',
  email: '[email protected]',
  name: 'John Doe',
  // ... any custom properties
});

Set Properties

// Set profile properties
openpanel?.setProfile({
  plan: 'premium',
  company: 'Acme Inc'
});

// Increment a property
openpanel?.increment('page_views', 1);

// Decrement a property
openpanel?.decrement('credits', 5);

Clear/Reset

// Clear the current user (logout)
openpanel?.clear();

TypeScript Support

All types from @openpanel/sdk are re-exported for your convenience:

import { 
  OpenPanelProvider, 
  useOpenPanel,
  type OpenPanelOptions,
  type TrackProperties
} from '@openpanel/react';

// Typed event tracking
const properties: TrackProperties = {
  product_id: 'prod-123',
  amount: 99.99
};

openpanel?.track('purchase', properties);

Server-Side Rendering (SSR)

The provider is client-side only ('use client' directive) and safely handles SSR environments:

  • ✅ Works with Next.js App Router and Pages Router
  • ✅ OpenPanel client only initializes in the browser (typeof window !== 'undefined')
  • ✅ No hydration errors
  • ✅ Safe to use in Server Components (wrap client components that use the hook)

Example with Server Component:

// app/page.tsx (Server Component)
import { AnalyticsButton } from './analytics-button';

export default function Page() {
  return (
    <div>
      <h1>My Page</h1>
      <AnalyticsButton />
    </div>
  );
}
// app/analytics-button.tsx (Client Component)
'use client';

import { useOpenPanel } from '@openpanel/react';

export function AnalyticsButton() {
  const openpanel = useOpenPanel();
  
  return (
    <button onClick={() => openpanel?.track('button_click')}>
      Click Me
    </button>
  );
}

Advanced Usage

Custom Client Instance

You can extend the OpenPanel class for custom behavior:

import { OpenPanel as OpenPanelBase, type OpenPanelOptions } from '@openpanel/react';

class CustomOpenPanel extends OpenPanelBase {
  constructor(options: OpenPanelOptions) {
    super(options);
    
    // Add custom initialization
    this.track('app_initialized');
  }
  
  // Add custom methods
  trackPageView(pageName: string) {
    this.track('page_view', { page: pageName });
  }
}

Error Handling

The useOpenPanel() hook throws an error if used outside the provider:

try {
  const openpanel = useOpenPanel();
} catch (error) {
  console.error('OpenPanel not available:', error);
}

Always ensure components using the hook are wrapped in <OpenPanelProvider>.

Summary by CodeRabbit

  • New Features

    • Released official React SDK with provider-based integration pattern and hooks support for seamless event tracking and user identification.
  • Documentation

    • Added comprehensive React SDK documentation covering installation, usage patterns, environment configuration, and TypeScript support.

@vercel
Copy link

vercel bot commented Nov 2, 2025

@ian is attempting to deploy a commit to the Coderax's projects Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant
Copy link

CLAassistant commented Nov 2, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 2, 2025

Walkthrough

A new React SDK package is introduced with a provider-based architecture, TypeScript configuration, build tooling, and comprehensive documentation. The implementation includes React context, custom hooks, and a provider component for client-side initialization using environment variables or explicit configuration.

Changes

Cohort / File(s) Summary
React SDK Core Implementation
packages/sdks/react/index.tsx
Introduces OpenPanel class extending OpenPanelBase with React defaults (sdk: 'react', sdkVersion: '19.0.0'), useOpenPanel hook for accessing context-bound instance, and OpenPanelProvider component for lazy client-side initialization with environment variable support. Re-exports all base SDK exports.
Build & Package Configuration
packages/sdks/react/package.json, packages/sdks/react/tsconfig.json, packages/sdks/react/tsup.config.ts
Adds package metadata, TypeScript configuration, and tsup build setup for the React SDK. Declares @openpanel/sdk dependency, React/React-DOM peer dependencies (16.8–19.x), and build output targeting CommonJS and ESM formats with declaration generation.
React SDK Documentation
apps/public/content/docs/sdks/react.mdx
Expands minimal React doc stub into comprehensive guide covering About, Installation, Usage patterns (Client Components, Tracking Events, User Identification, Property Types, SSR), Environment Variables, Advanced Usage, and TypeScript support with code examples.

Sequence Diagram

sequenceDiagram
    participant App as React App
    participant Provider as OpenPanelProvider
    participant Ctx as OpenPanel Context
    participant Hook as useOpenPanel Hook
    participant Client as OpenPanel Instance

    Note over Provider: Component mounts (client-side only)
    Provider->>Provider: Read clientId or env vars
    Provider->>Client: Lazy initialize OpenPanel
    Provider->>Ctx: Store instance in context
    
    App->>Hook: Call useOpenPanel()
    Hook->>Ctx: Access context value
    Ctx-->>Hook: Return OpenPanel instance
    Hook-->>App: Provide instance for use
    
    Note over App,Client: Consumer can track events, identify users, etc.
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

  • packages/sdks/react/index.tsx: Review React context/provider pattern implementation, hook error handling, lazy initialization logic, and environment variable precedence
  • Documentation expansion: Verify accuracy of code examples, completeness of usage patterns, and consistency with SDK behavior
  • Configuration files: Standard setup but verify dependency versions, tsup build output targets, and TypeScript configuration alignment with workspace

Poem

🐰 A React SDK hops into view,
With hooks and providers brand new!
Context flows through your component tree,
Tracking events with providers so free.
Environment whispers guide the way—
OpenPanel thrives in React today! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Adding @openpanel/react sdk + docs" accurately and concisely captures the main changes introduced in this pull request. The changeset implements a new React SDK package (@openpanel/react) with a provider pattern, hooks, and comprehensive MDX documentation, all of which are clearly reflected in the title. The title is specific, uses clear language, avoids vague terms, and would allow developers scanning git history to immediately understand the primary purpose of the changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ian ian changed the title adding react sdk Adding @openpanel/react sdk with docs Nov 2, 2025
@ian ian marked this pull request as ready for review November 2, 2025 16:47
@ian ian changed the title Adding @openpanel/react sdk with docs Adding @openpanel/react sdk + docs Nov 2, 2025
@ian ian mentioned this pull request Nov 2, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
packages/sdks/react/tsup.config.ts (1)

3-12: Build configuration looks solid.

The tsup configuration correctly externalizes React dependencies and generates both CJS and ESM outputs with type declarations.

Consider enabling sourcemaps to improve the debugging experience for library consumers:

   sourcemap: false,
+  sourcemap: true,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c67f7f9 and 867177e.

📒 Files selected for processing (5)
  • apps/public/content/docs/sdks/react.mdx (1 hunks)
  • packages/sdks/react/index.tsx (1 hunks)
  • packages/sdks/react/package.json (1 hunks)
  • packages/sdks/react/tsconfig.json (1 hunks)
  • packages/sdks/react/tsup.config.ts (1 hunks)
🔇 Additional comments (2)
packages/sdks/react/tsconfig.json (1)

1-8: LGTM! Clean TypeScript configuration.

The configuration appropriately extends the base config, sets the output directory to match the build tooling, and excludes build artifacts from compilation.

apps/public/content/docs/sdks/react.mdx (1)

118-120: Verify environment variable precedence matches implementation.

The documented precedence order looks correct based on the implementation in index.tsx (Lines 91-95), where NEXT_PUBLIC_OPENPANEL_CLIENT_ID is checked before OPENPANEL_CLIENT_ID.

</Tab>
</Tabs>

This package requires React 18+ or React 19+.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify React version requirement against package.json.

The documentation states "This package requires React 18+ or React 19+", but the package.json (Line 13) declares peer dependencies starting from React 16.8.0. Please align the documentation with the actual peer dependency requirements or update the package.json if React 18+ is truly the minimum supported version.

Run this script to confirm the declared peer dependencies:


🏁 Script executed:

#!/bin/bash
# Verify the React peer dependency version in package.json
cat packages/sdks/react/package.json | jq -r '.peerDependencies.react'

Length of output: 40


Update documentation to reflect the actual React peer dependency range: 16.8.0+, 17+, 18+, and 19+.

The package.json declares peer dependencies as ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0, but the documentation at line 35 states "This package requires React 18+ or React 19+". Either update the documentation to accurately reflect the broader support (including React 16.8+ and 17+), or narrow the peer dependencies in package.json to ^18.0.0 || ^19.0.0 if that's the intended minimum version.

🤖 Prompt for AI Agents
In apps/public/content/docs/sdks/react.mdx around line 35, the doc text says
"This package requires React 18+ or React 19+" but package.json peerDependencies
allow ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0; update the documentation to list
the full supported range (React 16.8+, 17+, 18+, and 19+) to match package.json,
or if the intended minimum is actually 18+, update package.json to only ^18.0.0
|| ^19.0.0—choose one and make the doc and package.json consistent.

Comment on lines +23 to +31
export class OpenPanel extends OpenPanelBase {
constructor(options: OpenPanelOptions) {
super({
...options,
sdk: 'react',
sdkVersion: '19.0.0',
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Report actual React version instead of hardcoding '19.0.0'.

The sdkVersion is hardcoded to '19.0.0', but this package supports React 16.8 through 19.x (per package.json peer dependencies). This will send misleading analytics data about which React version is actually being used.

Apply this diff to report the actual React version:

+import React from 'react';
+
 export class OpenPanel extends OpenPanelBase {
   constructor(options: OpenPanelOptions) {
     super({
       ...options,
       sdk: 'react',
-      sdkVersion: '19.0.0',
+      sdkVersion: React.version,
     });
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export class OpenPanel extends OpenPanelBase {
constructor(options: OpenPanelOptions) {
super({
...options,
sdk: 'react',
sdkVersion: '19.0.0',
});
}
}
import React from 'react';
export class OpenPanel extends OpenPanelBase {
constructor(options: OpenPanelOptions) {
super({
...options,
sdk: 'react',
sdkVersion: React.version,
});
}
}
🤖 Prompt for AI Agents
In packages/sdks/react/index.tsx around lines 23 to 31, remove the hardcoded
sdkVersion '19.0.0' and replace it with logic that reports the actual React
version: at module startup attempt to read React.version from the installed
react package (require/import 'react' and use .version); if that is unavailable
(e.g., at build time), fall back to reading the peerDependency range for "react"
from this package's package.json and normalize it to a sensible version string
or "unknown" as a last resort; pass that resolved version string as sdkVersion
when calling super.

Comment on lines +57 to +63
export function useOpenPanel() {
const context = useContext(OpenPanelContext);
if (context === undefined) {
throw new Error('useOpenPanel must be used within OpenPanelProvider');
}
return context;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Error boundary check will never trigger.

The check context === undefined on Line 59 will never be true because the context is initialized with null as the default value (Line 43), not undefined. This means the error "useOpenPanel must be used within OpenPanelProvider" will never be thrown, even when the hook is used outside the provider.

The current behavior allows the hook to return null when used outside the provider, which the documentation examples handle with optional chaining (op?.track()). However, if the intention is to throw an error for misuse, the check needs to be fixed:

 export function useOpenPanel() {
   const context = useContext(OpenPanelContext);
-  if (context === undefined) {
-    throw new Error('useOpenPanel must be used within OpenPanelProvider');
-  }
   return context;
 }

Alternatively, if you want to enforce the error boundary, change the context default:

-const OpenPanelContext = createContext<OpenPanel | null>(null);
+const OpenPanelContext = createContext<OpenPanel | null | undefined>(undefined);

Then update the return type of useOpenPanel() and the check becomes valid.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function useOpenPanel() {
const context = useContext(OpenPanelContext);
if (context === undefined) {
throw new Error('useOpenPanel must be used within OpenPanelProvider');
}
return context;
}
export function useOpenPanel() {
const context = useContext(OpenPanelContext);
return context;
}
🤖 Prompt for AI Agents
In packages/sdks/react/index.tsx around lines 57 to 63, the guard `context ===
undefined` never fires because the context default is `null`; update the check
to `context === null` (or `context == null`) to correctly detect missing
provider, or alternatively change the Context default from `null` to `undefined`
and adjust the context type/return type accordingly so the `undefined` check is
valid; ensure the hook's return type reflects the chosen non-nullable value and
throw the error when the context is missing.

Comment on lines +83 to +110
export function OpenPanelProvider({
children,
clientId,
...options
}: OpenPanelProviderProps) {
const openpanelRef = useRef<OpenPanel | null>(null);

if (!openpanelRef.current && typeof window !== 'undefined') {
const id =
clientId ||
process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID ||
process.env.OPENPANEL_CLIENT_ID ||
'';

if (id) {
openpanelRef.current = new OpenPanel({
clientId: id,
...options,
});
}
}

return (
<OpenPanelContext.Provider value={openpanelRef.current}>
{children}
</OpenPanelContext.Provider>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Move OpenPanel initialization to useEffect or useMemo.

The OpenPanel instance is being initialized during render (Lines 90-103), which violates React's rules. Render functions should be pure and side-effect free. Initialization logic with side effects (like creating SDK instances) should be in useEffect or memoized with useMemo.

According to the React 19 documentation, this pattern can cause issues with concurrent rendering and Strict Mode. Apply this diff to fix:

 export function OpenPanelProvider({
   children,
   clientId,
   ...options
 }: OpenPanelProviderProps) {
-  const openpanelRef = useRef<OpenPanel | null>(null);
-
-  if (!openpanelRef.current && typeof window !== 'undefined') {
-    const id =
-      clientId ||
-      process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID ||
-      process.env.OPENPANEL_CLIENT_ID ||
-      '';
-
-    if (id) {
-      openpanelRef.current = new OpenPanel({
-        clientId: id,
-        ...options,
-      });
-    }
-  }
+  const openpanelRef = useRef<OpenPanel | null>(null);
+
+  useEffect(() => {
+    if (!openpanelRef.current && typeof window !== 'undefined') {
+      const id =
+        clientId ||
+        process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID ||
+        process.env.OPENPANEL_CLIENT_ID ||
+        '';
+
+      if (id) {
+        openpanelRef.current = new OpenPanel({
+          clientId: id,
+          ...options,
+        });
+      }
+    }
+  }, [clientId, options]);
 
   return (
     <OpenPanelContext.Provider value={openpanelRef.current}>

Note: You'll need to import useEffect from React.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function OpenPanelProvider({
children,
clientId,
...options
}: OpenPanelProviderProps) {
const openpanelRef = useRef<OpenPanel | null>(null);
if (!openpanelRef.current && typeof window !== 'undefined') {
const id =
clientId ||
process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID ||
process.env.OPENPANEL_CLIENT_ID ||
'';
if (id) {
openpanelRef.current = new OpenPanel({
clientId: id,
...options,
});
}
}
return (
<OpenPanelContext.Provider value={openpanelRef.current}>
{children}
</OpenPanelContext.Provider>
);
}
export function OpenPanelProvider({
children,
clientId,
...options
}: OpenPanelProviderProps) {
const openpanelRef = useRef<OpenPanel | null>(null);
useEffect(() => {
if (!openpanelRef.current && typeof window !== 'undefined') {
const id =
clientId ||
process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID ||
process.env.OPENPANEL_CLIENT_ID ||
'';
if (id) {
openpanelRef.current = new OpenPanel({
clientId: id,
...options,
});
}
}
}, [clientId, options]);
return (
<OpenPanelContext.Provider value={openpanelRef.current}>
{children}
</OpenPanelContext.Provider>
);
}
🤖 Prompt for AI Agents
In packages/sdks/react/index.tsx around lines 83 to 110, the OpenPanel instance
is being created during render; move this initialization into a React hook to
avoid render-side effects: keep the useRef<OpenPanel|null> for storage, import
useEffect (or useMemo) from React, and inside a useEffect that depends on
clientId and any relevant options compute the effective client id (clientId ||
env vars || ''), then if id and ref.current is null set ref.current = new
OpenPanel({ clientId: id, ...options }); optionally return a cleanup from
useEffect to dispose/close the instance if the SDK exposes such a method; leave
the Provider rendering as-is using openpanelRef.current.

Comment on lines +91 to +95
const id =
clientId ||
process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID ||
process.env.OPENPANEL_CLIENT_ID ||
'';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Environment variable access may not work in all bundlers.

Direct access to process.env in client-side code (Lines 93-94) relies on bundler-specific behavior. Next.js handles this with special variable replacement, but plain React applications using other bundlers (Vite, Webpack without specific config) may not have process.env available at runtime in the browser.

Consider documenting this limitation or using a more portable approach. For now, verify that the target bundlers (Next.js, Vite, etc.) properly handle these environment variables.


🌐 Web query:

How do different bundlers (Vite, Webpack, Next.js) handle process.env variables in client-side code?

💡 Result:

Summary — how each handles env in client bundles:

  • Vite — uses import.meta.env; only variables defined in .env* or the shell and starting with VITE_ (plus built-ins like import.meta.env.MODE/PROD/DEV) are exposed to client code. Values are statically replaced at build time; dynamic key access (e.g. import.meta.env[key]) won't be inlined. [1]

  • Webpack — does not expose process.env automatically; you (or frameworks) must supply replacements (commonly via webpack.DefinePlugin). DefinePlugin performs compile‑time text replacement (e.g. replacing process.env.NODE_ENV with '"production"'), so client code sees the substituted literal. [2]

  • Next.js — server env vars are server-only. To expose a value to the browser you must prefix it NEXT_PUBLIC_; Next.js then inlines those values into the client bundle at build time (dynamic lookups may not be inlined). For runtime server-only access use server-side rendering/APIs. [3]

Sources:
[1] Vite — Env & Mode docs.
[2] webpack — DefinePlugin docs.
[3] Next.js — Configuring environment variables / bundling for the browser.


I have the web search results. Now I need to verify the context around the code to determine if this is a Next.js-specific SDK or a general React SDK intended for multiple bundlers.


🏁 Script executed:

#!/bin/bash
# Check the package.json and README near the SDK to understand scope
fd -t f 'package\.json|README' packages/sdks/react | head -10

# Look for any environment configuration or bundler-specific setup
find packages/sdks -type f \( -name '*.md' -o -name '*.config.*' -o -name 'package.json' \) | head -20

Length of output: 548


🏁 Script executed:

#!/bin/bash
# Check the actual package.json for this SDK
cat packages/sdks/react/package.json 2>/dev/null || echo "Not found at expected path"

# Also check one level up
cat packages/sdks/package.json 2>/dev/null || cat packages/package.json 2>/dev/null || echo "Parent package.json not found"

Length of output: 603


🏁 Script executed:

#!/bin/bash
# Look for any comments or context around the environment variable usage
rg -n -B5 -A5 'NEXT_PUBLIC_OPENPANEL' packages/sdks/react/ 2>/dev/null | head -40

Length of output: 1454


🏁 Script executed:

#!/bin/bash
# Check if there's documentation about bundler support
rg -n 'bundler|Webpack|Vite' packages/sdks/ --type md --type txt 2>/dev/null || echo "No bundler mentions found"

Length of output: 25


Environment variable access patterns are incompatible with this multi-bundler React SDK.

This is a generic React SDK (not Next.js-specific; separate packages/sdks/nextjs exists) with React 16.8+ peer dependencies. However, the code directly accesses process.env in client code:

  • Vite: No process.env available in client bundles; only import.meta.env with VITE_* prefix works
  • Webpack: No process.env by default; requires explicit DefinePlugin configuration
  • Next.js: Only NEXT_PUBLIC_* variables are exposed to the browser; accessing process.env.OPENPANEL_CLIENT_ID (unprefixed) will be undefined at runtime

Lines 93-94 will fail or return undefined in most environments outside of Next.js with proper DefinePlugin setup.

Recommended fix: Make clientId a required prop or provide it via a config object. If environment variable fallback is needed, document the requirement explicitly and consider using bundler detection to access variables correctly (e.g., import.meta.env for Vite, process.env for Next.js with documented Next.js-only fallback).

🤖 Prompt for AI Agents
In packages/sdks/react/index.tsx around lines 91 to 95, the code reads
process.env directly in client-side SDK which breaks in non-Next.js bundlers
(Vite, plain Webpack) and will be undefined in many runtimes; remove direct
process.env access and instead require clientId as a required prop or accept it
via a config parameter passed into the SDK, delete the environment-variable
fallback logic from the client bundle, and update the README to document that if
consumers want env-based defaults they must inject them at build time (e.g.,
Next.js NEXT_PUBLIC_*, Vite import.meta.env VITE_*, or DefinePlugin) or supply
the value to the SDK via configuration.

{
"name": "@openpanel/react",
"version": "1.0.1-local",
"module": "index.ts",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix module field to point to built output.

The "module" field currently points to the source file "index.ts", but it should point to the compiled ESM output. For a published package, consumers need the built artifacts, not the source TypeScript files.

Additionally, the package is missing critical fields for proper distribution:

  • "main": CJS entry point
  • "types": TypeScript declarations entry
  • "exports": Modern Node.js conditional exports

Apply this diff to fix the package entry points:

 {
   "name": "@openpanel/react",
   "version": "1.0.1-local",
-  "module": "index.ts",
+  "main": "./dist/index.js",
+  "module": "./dist/index.mjs",
+  "types": "./dist/index.d.ts",
+  "exports": {
+    ".": {
+      "import": "./dist/index.mjs",
+      "require": "./dist/index.js",
+      "types": "./dist/index.d.ts"
+    }
+  },
   "scripts": {

"version": "1.0.1-local",
"module": "index.ts",
"scripts": {
"build": "rm -rf dist && tsup",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make build script cross-platform compatible.

The rm -rf command will fail on Windows. Use a cross-platform alternative like rimraf or the built-in tsup --clean flag (which you already have in tsup.config.ts).

Apply this diff:

   "scripts": {
-    "build": "rm -rf dist && tsup",
+    "build": "tsup",
     "typecheck": "tsc --noEmit"
   },

The clean: true option in your tsup.config.ts already handles cleaning the dist directory.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"build": "rm -rf dist && tsup",
"scripts": {
"build": "tsup",
"typecheck": "tsc --noEmit"
},
🤖 Prompt for AI Agents
In packages/sdks/react/package.json around line 6, the build script uses "rm -rf
dist && tsup" which is not cross-platform; remove the manual rm -rf and rely on
tsup's cleaning (tsup.config.ts already sets clean: true) by replacing the
script with simply "tsup" (or "tsup --clean" if you prefer explicit flag), so
Windows users won't fail and no additional dep is required.

Comment on lines +13 to +14
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Peer dependency version conflicts with documentation.

The package.json declares support for React 16.8.0+, but the documentation (apps/public/content/docs/sdks/react.mdx Line 35) states "This package requires React 18+ or React 19+". Please align these requirements.

If React 18+ is the true minimum, update the peer dependencies:

   "peerDependencies": {
-    "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
-    "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+    "react": "^18.0.0 || ^19.0.0",
+    "react-dom": "^18.0.0 || ^19.0.0"
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
🤖 Prompt for AI Agents
In packages/sdks/react/package.json around lines 13 to 14, the peerDependencies
currently allow React 16/17 which conflicts with the docs requiring React 18+.
Update the peerDependencies for both "react" and "react-dom" to require React
18+ (e.g. change to "^18.0.0 || ^19.0.0" or an equivalent semver range that
excludes 16/17) so the package.json aligns with the documentation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used same peer deps as next package.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and display the Next.js package's package.json peer dependencies
echo "=== Next.js package peer dependencies ==="
fd -t f "package.json" packages/sdks/next --exec cat {} | jq -r '.peerDependencies'

echo -e "\n=== React package peer dependencies ==="
cat packages/sdks/react/package.json | jq -r '.peerDependencies'

echo -e "\n=== Checking React SDK implementation for React 18+ features ==="
# Check for React 18+ specific APIs like useId, useTransition, useDeferredValue, etc.
cat packages/sdks/react/index.tsx | head -100

Length of output: 3017


🏁 Script executed:

#!/bin/bash
# Search for any Next.js or next related packages in the entire repo
echo "=== Searching for Next.js related packages ==="
fd -t f "package.json" . -x grep -l "next" {} | head -10

echo -e "\n=== Checking all SDK packages ==="
fd -t f "package.json" packages/sdks -x bash -c 'echo "File: $1" && cat "$1" | jq -r ".name, .peerDependencies"' _ {}

echo -e "\n=== Checking documentation for React version requirements ==="
rg -n "React 18|React 19|requires React" apps/public/content/docs/sdks/react.mdx

Length of output: 1291


@ian, you're absolutely right! I've verified that @openpanel/nextjs has the exact same peer dependencies (^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0), and your React SDK implementation only uses basic hooks (createContext, useContext, useRef) that have been available since React 16.8.

The issue is actually with the documentation, not the peer dependencies. Line 35 in the docs states "This package requires React 18+ or React 19+", but this is overstated given that:

  • The code doesn't use any React 18+ specific features
  • It's intentionally aligned with the nextjs package's broader compatibility

You should update the documentation to reflect the actual requirement:

-This package requires React 18+ or React 19+.
+This package requires React 16.8+ (hooks support).

Or to match the exact peer dependency range:

-This package requires React 18+ or React 19+.
+This package supports React 16.8+, 17, 18, and 19.

My apologies for the confusion in my initial review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants